diff --git a/AppDelegate.h b/AppDelegate.h index c7cf856..5fed390 100755 --- a/AppDelegate.h +++ b/AppDelegate.h @@ -24,6 +24,16 @@ #import "FlaggedItemWindowController.h" #import "RequestRootWindowController.h" +/* GLOBALS */ + +//shared enumerator +extern TaskEnumerator* taskEnumerator; + +//shared virustotal object +extern VirusTotal* virusTotal; + +//network connected flag +extern BOOL isConnected; @interface AppDelegate : NSObject { @@ -36,9 +46,6 @@ //start time @property NSTimeInterval startTime; -//connection flag -@property BOOL isConnected; - //'filter task' search box // ->top pane @property (weak) IBOutlet NSSearchField *filterTasksBox; @@ -46,13 +53,9 @@ //(current) bottom view controller @property(nonatomic, retain)TaskTableController *bottomViewController; +//top view/pane @property (weak) IBOutlet NSView *topPane; -//@property (weak) IBOutlet NSScrollView *taskScrollView; - -//task enumerator object -@property(nonatomic, retain)TaskEnumerator* taskEnumerator; - //task table controller object @property (nonatomic, retain)TaskTableController *taskTableController; @@ -83,9 +86,6 @@ @property (weak) IBOutlet NSButton *logoButton; - -//@property (weak) IBOutlet NSButton *showPreferencesButton; - //spinner @property (weak) IBOutlet NSProgressIndicator *progressIndicator; @@ -95,9 +95,6 @@ //filter object @property(nonatomic, retain)Filter* filterObj; -//virus total object -@property(nonatomic, retain)VirusTotal* virusTotalObj; - //array for all virus total threads @property(nonatomic, retain)NSMutableArray* vtThreads; @@ -152,9 +149,6 @@ //top constraint @property(nonatomic, retain)NSLayoutConstraint* trailingConstraint; -//flagged items -@property(nonatomic, retain)NSMutableArray* flaggedItems; - //flag for filter field (autocomplete) @property BOOL completePosting; @@ -196,13 +190,6 @@ // ->query OS to refresh/reload all tasks -(IBAction)refreshTasks:(id)sender; -//callback when user has updated prefs -// ->reload table, etc -//-(void)applyPreferences; - -//button handler for when settings icon (gear) is clicked -//-(IBAction)showPreferences:(id)sender; - //button handler for logo -(IBAction)logoButtonHandler:(id)sender; @@ -241,10 +228,6 @@ //display (in separate popup) all flagged items -(IBAction)showFlaggedItems:(id)sender; -//save a flagged binary -// ->also set text flagged items button label to red --(void)saveFlaggedBinary:(Binary*)binary; - //callback for custom search fields // ->handle auto-complete filterings -(void)filterAutoComplete:(NSTextView*)textField; diff --git a/AppDelegate.m b/AppDelegate.m index d827e4d..5845505 100755 --- a/AppDelegate.m +++ b/AppDelegate.m @@ -10,8 +10,8 @@ #import "Consts.h" #import "Binary.h" #import "Update.h" -#import "Connection.h" #import "Utilities.h" +#import "Connection.h" #import "AppDelegate.h" #import "serviceInterface.h" #import "TaskTableController.h" @@ -29,13 +29,9 @@ @synthesize bottomPane; @synthesize saveButton; @synthesize currentTask; -@synthesize isConnected; -@synthesize flaggedItems; @synthesize searchButton; @synthesize viewSelector; @synthesize scannerThread; -@synthesize virusTotalObj; -@synthesize taskEnumerator; @synthesize taskViewFormat; @synthesize commandHandling; @synthesize completePosting; @@ -57,18 +53,18 @@ //kick off main window/logic -(void)taskExplore { + //make foreground so it has an dock icon, etc + transformProcess(kProcessTransformToForegroundApplication); + //for autolayout [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"]; //init virus total object - virusTotalObj = [[VirusTotal alloc] init]; + virusTotal = [[VirusTotal alloc] init]; //init filter obj filterObj = [[Filter alloc] init]; - //alloc flagged items - flaggedItems = [NSMutableArray array]; - //alloc/init custom search field for tasks customTasksFilter = [[CustomTextField alloc] init]; @@ -123,7 +119,7 @@ if(YES != [self isAuthenticated]) { //display auth popup - // ->will invoke 'go' method on successful auth + // will invoke 'go' method on successful auth [self askForRoot]; } //go! @@ -171,12 +167,9 @@ // ->main entry point -(void)applicationDidFinishLaunching:(NSNotification *)notification { - //init crash reporting - initCrashReporting(); - //first time run? // show thanks to friends window! - // note: on close invokes method to show main window + // note: on close, invokes method to show main window if(YES != [[NSUserDefaults standardUserDefaults] boolForKey:NOT_FIRST_TIME]) { //set key @@ -209,8 +202,10 @@ //register handler for hot keys -(void)registerKeypressHandler { - NSEvent * (^keypressHandler)(NSEvent *); + //handler + NSEvent * (^keypressHandler)(NSEvent *) = nil; + //handler block keypressHandler = ^NSEvent * (NSEvent * theEvent){ return [self handleKeypress:theEvent]; @@ -440,7 +435,7 @@ bail: -(void)exploreTasks { //alloc task enumerator - if(nil == self.taskEnumerator) + if(nil == taskEnumerator) { //alloc taskEnumerator = [[TaskEnumerator alloc] init]; @@ -448,7 +443,7 @@ bail: //kick off thread to enum task // ->will update table as results come in - [NSThread detachNewThreadSelector:@selector(enumerateTasks) toTarget:self.taskEnumerator withObject:nil]; + [NSThread detachNewThreadSelector:@selector(enumerateTasks) toTarget:taskEnumerator withObject:nil]; return; } @@ -532,10 +527,10 @@ bail: if(YES != self.taskTableController.isFiltered) { //sync to get row - @synchronized (self.taskEnumerator.tasks) + @synchronized (taskEnumerator.tasks) { //get row - row = [self.taskEnumerator.tasks indexOfKey:((Task*)item).pid]; + row = [taskEnumerator.tasks indexOfKey:((Task*)item).pid]; } } //filtering @@ -816,11 +811,11 @@ bail: if(YES != self.taskTableController.isFiltered) { //sync - @synchronized(self.taskEnumerator.tasks) + @synchronized(taskEnumerator.tasks) { //get tasks - tasks = self.taskEnumerator.tasks; + tasks = taskEnumerator.tasks; //reload each row w/ new VT info for(NSNumber* taskPid in tasks) @@ -871,6 +866,35 @@ bail: [self reloadRow:binary]; } + //main thread + //update 'flagged' item + // ...method might be invoked by VT callback, etc + dispatch_async(dispatch_get_main_queue(), ^{ + + //flagged items? + // set icon to red + if(0 != taskEnumerator.flaggedItems.count) + { + //set main image + self.flaggedButton.image = [NSImage imageNamed:@"flaggedRed"]; + + //set alternate image + self.flaggedButton.alternateImage = [NSImage imageNamed:@"flaggedRedBG"]; + } + //no flagged items + // set icon back to default + else + { + //set main image + self.flaggedButton.image = [NSImage imageNamed:@"flagged"]; + + //set alternate image + self.flaggedButton.alternateImage = [NSImage imageNamed:@"flaggedBG"]; + } + + }); + + return; } @@ -965,7 +989,7 @@ bail: -(void)reloadTaskTable { //sort tasks - [self sortTasksForView:self.taskEnumerator.tasks]; + [self sortTasksForView:taskEnumerator.tasks]; //when exec'ing on background thread // ->exec on main thread @@ -991,62 +1015,6 @@ bail: return; } -//callback when user has updated prefs -// ->reload table, etc --(void)applyPreferences -{ - //currently selected category - //NSUInteger selectedCategory = 0; - - /* - - //get currently selected category - //selectedCategory = self.categoryTableController.categoryTableView.selectedRow; - - //reload category table - [self.categoryTableController customReload]; - - //reloading the category table resets the selected plugin - // ->so manually (re)set it here - self.selectedPlugin = self.plugins[selectedCategory]; - - //reload item table - [self.taskTableController.itemTableView reloadData]; - - //if VT query was never done (e.g. scan was started w/ pref disabled) - // ->kick off VT queries now - if( (0 == self.vtThreads.count) && - (YES != self.prefsWindowController.disableVTQueries) ) - { - //iterate over all plugins - // ->do VT query for each - for(PluginBase* plugin in self.plugins) - { - //do query - [self queryVT:plugin]; - } - } - - //save results? - // ->if there was a previous scan - if( (nil != self.scannerThread) && - (YES == self.prefsWindowController.shouldSaveNow)) - { - //save - [self saveResults]; - - //alloc/init alert - saveAlert = [NSAlert alertWithMessageText:[NSString stringWithFormat:@"current results saved to %@", OUTPUT_FILE] defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"subsequent scans will overwrite this file"]; - - //show it - [saveAlert runModal]; - } - - */ - - return; -} - //automatically invoked when window is closing // ->terminate app -(void)windowWillClose:(NSNotification *)notification @@ -1128,7 +1096,7 @@ bail: else if(FLAGGED_BUTTON_TAG == tag) { //when no flagged items - if(0 == self.flaggedItems.count) + if(0 == taskEnumerator.flaggedItems.count) { //set imageName = @"flagged"; @@ -1175,7 +1143,7 @@ bail: else if(FLAGGED_BUTTON_TAG == tag) { //when no flagged items - if(0 == self.flaggedItems.count) + if(0 == taskEnumerator.flaggedItems.count) { //set imageName = @"flaggedOver"; @@ -1250,14 +1218,14 @@ bail: [output appendString:@"{\"tasks:\":["]; //sync - @synchronized(self.taskEnumerator.tasks) + @synchronized(taskEnumerator.tasks) { //get tasks - for(NSNumber* taskPid in self.taskEnumerator.tasks) + for(NSNumber* taskPid in taskEnumerator.tasks) { //append task JSON - [output appendFormat:@"{%@},", [self.taskEnumerator.tasks[taskPid] toJSON]]; + [output appendFormat:@"{%@},", [taskEnumerator.tasks[taskPid] toJSON]]; } }//sync @@ -1281,7 +1249,6 @@ bail: //init popup w/ error msg saveResultPopup = [NSAlert alertWithMessageText:@"ERROR: failed to save output" defaultButton:@"Ok" alternateButton:nil otherButton:nil informativeTextWithFormat:@"details: %@", error]; - } //happy // ->set result msg @@ -1506,7 +1473,7 @@ bail: ( (YES == self.taskTableController.isFiltered) && (0 != self.taskTableController.filteredItems.count) ) ) { //set to first - self.currentTask = self.taskEnumerator.tasks[[self.taskEnumerator.tasks keyAtIndex:0]]; + self.currentTask = taskEnumerator.tasks[[taskEnumerator.tasks keyAtIndex:0]]; } } @@ -1576,7 +1543,7 @@ bail: //(re)enumerate dylibs via XPC // ->triggers table reload when done - [self.currentTask enumerateDylibs:self.taskEnumerator.dylibs shouldWait:NO]; + [self.currentTask enumerateDylibs:taskEnumerator.dylibs shouldWait:NO]; break; @@ -1731,7 +1698,7 @@ bail: [self.taskTableController.filteredItems removeAllObjects]; //normal filter - [self.filterObj filterTasks:search.string items:self.taskEnumerator.tasks results:self.taskTableController.filteredItems pane:PANE_TOP]; + [self.filterObj filterTasks:search.string items:taskEnumerator.tasks results:self.taskTableController.filteredItems pane:PANE_TOP]; }//sync @@ -1835,8 +1802,20 @@ bail: //maks self.filteringOverlay.layer.masksToBounds = YES; - //set overlay's view color to gray - self.filteringOverlay.layer.backgroundColor = NSColor.grayColor.CGColor; + //dark mode + // set overlay to light + if(YES == isDarkMode()) + { + //light gray + self.filteringOverlay.layer.backgroundColor = NSColor.lightGrayColor.CGColor; + } + //light mode + // set overlay to gray + else + { + //gray + self.filteringOverlay.layer.backgroundColor = NSColor.grayColor.CGColor; + } //make it semi-transparent self.filteringOverlay.alphaValue = 0.95; @@ -2084,43 +2063,6 @@ bail: return; } -//save a flagged item -// ->also set text flagged items button label to red --(void)saveFlaggedBinary:(Binary*)binary -{ - //first check if item is already flagged - if(YES == [self.flaggedItems containsObject:binary]) - { - //no need to add - // ->so bail - goto bail; - } - - //sync to save - @synchronized(self.flaggedItems) - { - //save - [self.flaggedItems addObject:binary]; - } - - //when count is 1 - // ->means first flagged file so set image to red - if(1 == self.flaggedItems.count) - { - //set main image - [self.flaggedButton setImage:[NSImage imageNamed:@"flaggedRed"]]; - - //set alternate image - [self.flaggedButton setAlternateImage:[NSImage imageNamed:@"flaggedRedBG"]]; - - } - -//bail -bail: - - return; -} - //button handle for 'flagged items' button // ->display (in separate popup) all flagged items -(IBAction)showFlaggedItems:(id)sender @@ -2130,7 +2072,7 @@ bail: //handle case where there aren't any flagged items // ->just show alert - if(0 == self.flaggedItems.count) + if(0 == taskEnumerator.flaggedItems.count) { //alloc/init alert alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:@"No items flagged by VirusTotal"] defaultButton:@"Ok" alternateButton:nil otherButton:nil informativeTextWithFormat:@"hooray! 😇"]; @@ -2321,7 +2263,7 @@ bail: [self finalizeFiltration:PANE_TOP]; //filter - [self.filterObj filterTasks:filterString items:self.taskEnumerator.tasks results:self.taskTableController.filteredItems pane:PANE_TOP]; + [self.filterObj filterTasks:filterString items:taskEnumerator.tasks results:self.taskTableController.filteredItems pane:PANE_TOP]; } //refresh/update UI when not a keyword search diff --git a/Consts.h b/Consts.h index 905b315..ab27086 100644 --- a/Consts.h +++ b/Consts.h @@ -30,6 +30,11 @@ //success #define STATUS_SUCCESS 0 +//user name +#define USER_NAME @"userName" + +//user (home) directory +#define USER_DIRECTORY @"userDirectory" //signers enum Signer{None, Apple, AppStore, DevID, AdHoc}; diff --git a/FlaggedItemWindowController.m b/FlaggedItemWindowController.m index feed9c7..53ff96b 100644 --- a/FlaggedItemWindowController.m +++ b/FlaggedItemWindowController.m @@ -64,14 +64,14 @@ } //populate flagged items array - for(Binary* flaggedItem in ((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems) + for(Binary* flaggedItem in taskEnumerator.flaggedItems) { //when binary is task binary // ->find/save all tasks instances if(YES == flaggedItem.isTaskBinary) { //get all tasks instances - flaggedTasks = [((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator tasksForBinary:flaggedItem]; + flaggedTasks = [taskEnumerator tasksForBinary:flaggedItem]; //sync to save @synchronized(self.flaggedItems) diff --git a/ItemView.m b/ItemView.m index de84b04..ecd8b5c 100644 --- a/ItemView.m +++ b/ItemView.m @@ -306,8 +306,8 @@ NSAttributedString* initBinaryString(id item, BOOL isSearchWindow) //add name [taskString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:binary.name]]; - //init gray color for pid - attributes = [NSDictionary dictionaryWithObject:NSColor.grayColor forKey:NSForegroundColorAttributeName]; + //init default color for pid + attributes = [NSDictionary dictionaryWithObject:NSColor.controlTextColor forKey:NSForegroundColorAttributeName]; //search window // ->only for tasks, since dylibs in search window are handled elsewhere ('loaded in') @@ -334,8 +334,7 @@ NSAttributedString* initBinaryString(id item, BOOL isSearchWindow) (YES == binary.isPacked) ) { //init color for comma, etc - // ->light gray - attributes = [NSDictionary dictionaryWithObject:NSColor.grayColor forKey:NSForegroundColorAttributeName]; + attributes = [NSDictionary dictionaryWithObject:NSColor.controlTextColor forKey:NSForegroundColorAttributeName]; //tasks //add comma string @@ -376,7 +375,7 @@ NSAttributedString* initBinaryString(id item, BOOL isSearchWindow) (YES != binary.notFound)) { //init color for closing - attributes = [NSDictionary dictionaryWithObject:NSColor.grayColor forKey:NSForegroundColorAttributeName]; + attributes = [NSDictionary dictionaryWithObject:NSColor.controlTextColor forKey:NSForegroundColorAttributeName]; //close string [taskString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@")" attributes:attributes]]; @@ -392,8 +391,7 @@ NSAttributedString* initBinaryString(id item, BOOL isSearchWindow) (YES == binary.isPacked) ) { //init color for comma, - // ->light gray - attributes = [NSDictionary dictionaryWithObject:NSColor.grayColor forKey:NSForegroundColorAttributeName]; + attributes = [NSDictionary dictionaryWithObject:NSColor.controlTextColor forKey:NSForegroundColorAttributeName]; //add [taskString appendAttributedString:[[NSAttributedString alloc] initWithString:@", " attributes:attributes]]; @@ -402,8 +400,7 @@ NSAttributedString* initBinaryString(id item, BOOL isSearchWindow) else { //init color for comma, etc - // ->light gray - attributes = [NSDictionary dictionaryWithObject:NSColor.grayColor forKey:NSForegroundColorAttributeName]; + attributes = [NSDictionary dictionaryWithObject:NSColor.controlTextColor forKey:NSForegroundColorAttributeName]; //tasks //add comma string @@ -427,14 +424,14 @@ NSAttributedString* initBinaryString(id item, BOOL isSearchWindow) attributes = [NSDictionary dictionaryWithObject:[NSColor redColor] forKey:NSForegroundColorAttributeName]; //add - [taskString appendAttributedString:[[NSAttributedString alloc] initWithString:@"not found" attributes:attributes]]; + [taskString appendAttributedString:[[NSAttributedString alloc] initWithString:@"deleted" attributes:attributes]]; //dylib, need to close string here // ->normally it doesn't have anything after... if(YES != [item isKindOfClass:[Task class]]) { //init color for closing - attributes = [NSDictionary dictionaryWithObject:NSColor.grayColor forKey:NSForegroundColorAttributeName]; + attributes = [NSDictionary dictionaryWithObject:NSColor.controlTextColor forKey:NSForegroundColorAttributeName]; //close string [taskString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@")" attributes:attributes]]; @@ -448,8 +445,7 @@ NSAttributedString* initBinaryString(id item, BOOL isSearchWindow) if(YES == [item isKindOfClass:[Task class]]) { //init color for closing - // ->light gray - attributes = [NSDictionary dictionaryWithObject:NSColor.grayColor forKey:NSForegroundColorAttributeName]; + attributes = [NSDictionary dictionaryWithObject:NSColor.controlTextColor forKey:NSForegroundColorAttributeName]; //close string [taskString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@")" attributes:attributes]]; @@ -478,7 +474,7 @@ NSAttributedString* initLoadedInString(id item) //get host tasks // ->works with dylibs or files - tasks = [((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator loadedIn:item]; + tasks = [taskEnumerator loadedIn:item]; //dylibs/files // ->add name @@ -497,8 +493,7 @@ NSAttributedString* initLoadedInString(id item) } //init color for 'loaded in...' - // ->light gray - attributes = [NSDictionary dictionaryWithObject:NSColor.grayColor forKey:NSForegroundColorAttributeName]; + attributes = [NSDictionary dictionaryWithObject:NSColor.controlTextColor forKey:NSForegroundColorAttributeName]; //add dylib indicator //-> '(dylib, loaded in: ... ' @@ -851,8 +846,8 @@ void configVTButton(NSTableCellView *itemCell, id owner, Binary* binary) //set string (vt ratio), with attributes [vtButton setAttributedTitle:[[NSAttributedString alloc] initWithString:vtDetectionRatio attributes:stringAttributes]]; - //set color (gray) - stringAttributes[NSForegroundColorAttributeName] = NSColor.grayColor; + //set color + stringAttributes[NSForegroundColorAttributeName] = NSColor.controlTextColor; //set selected text color [vtButton setAttributedAlternateTitle:[[NSAttributedString alloc] initWithString:vtDetectionRatio attributes:stringAttributes]]; diff --git a/Items/Binary.h b/Items/Binary.h index 525442d..15937c6 100644 --- a/Items/Binary.h +++ b/Items/Binary.h @@ -31,6 +31,10 @@ //flag for task (main) executable @property BOOL isTaskBinary; +//loaded in +// ...for dylibs only +@property(nonatomic, retain)NSArray* loadedIn; + //hashes (md5, sha1) @property(nonatomic, retain)NSDictionary* hashes; @@ -49,6 +53,7 @@ //not found @property BOOL notFound; + /* VIRUS TOTAL INFO */ //dictionary returned by VT diff --git a/Items/Binary.m b/Items/Binary.m index 437686e..e414668 100644 --- a/Items/Binary.m +++ b/Items/Binary.m @@ -22,6 +22,7 @@ @synthesize parser; @synthesize vtInfo; @synthesize isPacked; +@synthesize loadedIn; @synthesize notFound; @synthesize isEncrypted; @synthesize signingInfo; @@ -52,6 +53,8 @@ //determine if its on disk self.notFound = ![[NSFileManager defaultManager] fileExistsAtPath:self.path]; + //get attributes + self.attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.path error:nil]; } //bail @@ -217,13 +220,8 @@ bail: //computes hashes // ->set 'md5' and 'sha1' iVars self.hashes = hashFile(self.path); - - //call into filter object to check if file is known - // ->apple-signed or whitelisted - //self.isTrusted = [((AppDelegate*)[[NSApplication sharedApplication] delegate]).filterObj isTrustedFile:self]; - + return; - } //format the signing info dictionary @@ -356,10 +354,10 @@ bail: -(NSString*)toJSON { //json string - NSString *json = nil; + NSMutableString *json = nil; //json data - // ->for intermediate conversions + // for intermediate conversions NSData *jsonData = nil; //hashes @@ -371,19 +369,25 @@ bail: //VT detection ratio NSString* vtDetectionRatio = nil; + //tasks loaded in + NSMutableArray* taskPids = nil; + + //'loaded in' list + NSString* tasks = nil; + //init file hash to default string - // ->used when hashes are nil, or serialization fails + // used when hashes are nil, or serialization fails fileHashes = @"\"unknown\""; //init file signature to default string - // ->used when signatures are nil, or serialization fails + // used when signatures are nil, or serialization fails fileSigs = @"\"unknown\""; //convert hashes to JSON if(nil != self.hashes) { //convert hash dictionary - // ->wrap since we are serializing JSON + // wrap since we are serializing JSON @try { //convert @@ -395,7 +399,7 @@ bail: } } //ignore exceptions - // ->file hashes will just be 'unknown' + // file hashes will just be 'unknown' @catch(NSException *exception) { ; @@ -406,7 +410,7 @@ bail: if(nil != self.signingInfo) { //convert signing dictionary - // ->wrap since we are serializing JSON + // wrap since we are serializing JSON @try { //convert @@ -424,15 +428,55 @@ bail: ; } } + + //dylibs + //covert 'loaded in' array to JSON + if(YES != isTaskBinary) + { + //init + taskPids = [NSMutableArray array]; + + //tasks loaded in + for(Task* task in self.loadedIn) + { + //add pid + [taskPids addObject:task.pid]; + } + //wrap since we are serializing JSON + @try + { + //convert + jsonData = [NSJSONSerialization dataWithJSONObject:taskPids options:kNilOptions error:NULL]; + if(nil != jsonData) + { + //convert data to string + tasks = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } + } + //ignore exceptions + // ->file sigs will just be 'unknown' + @catch(NSException *exception) + { + ; + } + } + //init VT detection ratio vtDetectionRatio = [NSString stringWithFormat:@"%lu/%lu", (unsigned long)[self.vtInfo[VT_RESULTS_POSITIVES] unsignedIntegerValue], (unsigned long)[self.vtInfo[VT_RESULTS_TOTAL] unsignedIntegerValue]]; //init json - json = [NSString stringWithFormat:@"\"name\": \"%@\", \"path\": \"%@\", \"hashes\": %@, \"signature(s)\": %@, \"VT detection\": \"%@\", \"encrypted\": %d, \"packed\": %d, \"not found\": %d", self.name, self.path, fileHashes, fileSigs, vtDetectionRatio, self.isEncrypted, self.isPacked, self.notFound]; + json = [NSMutableString stringWithFormat:@"\"name\": \"%@\", \"path\": \"%@\", \"hashes\": %@, \"signature(s)\": %@, \"VT detection\": \"%@\", \"encrypted\": %d, \"packed\": %d, \"deleted\": %d", self.name, self.path, fileHashes, fileSigs, vtDetectionRatio, self.isEncrypted, self.isPacked, self.notFound]; + + //dylibs + // add tasks they are loaded in + if(YES != self.isTaskBinary) + { + //add + [json appendString:[NSString stringWithFormat:@", \"loaded in\": \"%@\"", tasks]]; + } return json; } - @end diff --git a/Items/File.h b/Items/File.h index 1473ada..657775c 100644 --- a/Items/File.h +++ b/Items/File.h @@ -10,6 +10,10 @@ #import +/* GLOBALS */ + +//(privacy) protected directories +extern NSArray* protectedDirectories; @interface File : ItemBase { @@ -35,6 +39,4 @@ // ->invokes 'file' cmd, the parses out result -(void)setFileType; - - @end diff --git a/Items/File.m b/Items/File.m index 756752c..e27da8f 100644 --- a/Items/File.m +++ b/Items/File.m @@ -16,12 +16,11 @@ @synthesize type; - //init method -(id)initWithParams:(NSDictionary*)params { //super - // ->saves path, etc + // saves path, etc self = [super initWithParams:params]; if(self) { @@ -31,18 +30,22 @@ //set icon self.icon = [[NSWorkspace sharedWorkspace] iconForFile:self.path]; - //grab attributes - self.attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.path error:nil]; + //check if protected + // if not, get file attrs + if(YES != [self isProtected]) + { + //get attrs + self.attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.path error:nil]; + } } - -//bail + bail: return self; } //set file type -// ->invokes 'file' cmd, the parses out result +// invokes 'file' cmd, the parses out result -(void)setFileType { //results from 'file' cmd @@ -82,7 +85,6 @@ bail: // ->also trim whitespace self.type = [parsedResults[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; -//bail bail: return; @@ -100,14 +102,14 @@ bail: } //override method -// ->hash +// hash the file -(NSUInteger)hash { return [self.path hash]; } //override method -// ->equality check +// file equality check (path) -(BOOL)isEqual:(id)object { //flag @@ -143,12 +145,36 @@ bail: goto bail; } -//bail bail: return objEqual; } +//check if file is protected +// on mojave+, need to avoid prompts +-(BOOL)isProtected +{ + //flag + BOOL protected = NO; + + //skip any files in (privacy) protected directories + // as otherwise we will generate a privacy prompt (on Mojave) + for(NSString* directory in protectedDirectories) + { + //check + if(YES == [self.path hasPrefix:directory]) + { + //set flag + protected = YES; + + //done + break; + } + } + + return protected; +} + //convert object to JSON string -(NSString*)toJSON { @@ -162,7 +188,7 @@ bail: attributesJSON = [NSMutableString string]; //when attributes are nil - // ->init default string + // init default string, 'unknown' if(nil == self.attributes) { //init @@ -170,7 +196,7 @@ bail: } //file has attributes - // ->add each + // add each one to json else { //start @@ -208,5 +234,4 @@ bail: return json; } - @end diff --git a/Items/ItemBase.m b/Items/ItemBase.m index 82c58fd..9450ef9 100644 --- a/Items/ItemBase.m +++ b/Items/ItemBase.m @@ -29,10 +29,6 @@ //extract/save path self.path = params[KEY_RESULT_PATH]; - - //get attributes - // ->based off path - self.attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.path error:nil]; } return self; @@ -50,20 +46,10 @@ /* OPTIONAL METHODS */ - /* REQUIRED METHODS */ //stubs for inherited methods -// ->all just throw exceptions as they should be implemented in sub-classes - -//scan --(void)scan:(NSDictionary*)scanOptions -{ - @throw [NSException exceptionWithName:kExceptName - reason:[NSString stringWithFormat:kErrFormat, NSStringFromSelector(_cmd), [self class]] - userInfo:nil]; - return; -} +// throw exceptions as they should be implemented in sub-classes //convert object to JSON string -(NSString*)toJSON @@ -74,4 +60,4 @@ return nil; } -@end \ No newline at end of file +@end diff --git a/PrefsWindowController.m b/PrefsWindowController.m index f5af828..4dfb59d 100644 --- a/PrefsWindowController.m +++ b/PrefsWindowController.m @@ -82,44 +82,6 @@ return; } -/* -//save prefs --(void)savePrefs -{ - //first, any prefs changed, a 'save' set - // ->set 'save now' flag - if( ((self.showTrustedItems != self.showTrustedItemsBtn.state) || - (self.disableVTQueries != self.disableVTQueriesBtn.state) || - (self.saveOutput != self.saveOutputBtn.state) ) && - (YES == self.saveOutputBtn.state) ) - { - //set - self.shouldSaveNow = YES; - } - //don't save - else - { - //unset - self.shouldSaveNow = NO; - } - - //save hiding OS components flag - self.showTrustedItems = self.showTrustedItemsBtn.state; - - //save disabling VT flag - self.disableVTQueries = self.disableVTQueriesBtn.state; - - //save save output flag - self.saveOutput = self.saveOutputBtn.state; - - //call back up into app delegate for filtering/hiding OS components - [((AppDelegate*)[[NSApplication sharedApplication] delegate]) applyPreferences]; - - return; -} -*/ - - //'OK' button handler // ->save prefs and close window -(IBAction)closeWindow:(id)sender diff --git a/Queue.h b/Queue.h index 53f8ac4..bae2edb 100644 --- a/Queue.h +++ b/Queue.h @@ -6,7 +6,6 @@ // Copyright (c) 2014 Synack. All rights reserved. // - //from: https://github.com/esromneb/ios-queue-object/blob/master/NSMutableArray%2BQueueAdditions.h #import @@ -27,20 +26,27 @@ /* PROPERTIES */ +//items in +@property NSUInteger itemsIn; + +//items out +@property NSUInteger itemsOut; //event queue @property(retain, atomic)NSMutableArray* eventQueue; - +//thread to process events @property (nonatomic, retain)NSThread* qProcessorThread; -@property (nonatomic, retain)NSCondition* queueCondition; +//condition for queue +@property (nonatomic, retain)NSCondition* queueCondition; //METHODS //add an object to the queue -(void)enqueue:(id)anObject; - +//process events from queue +-(void)processQueue:(id)threadParam; @end diff --git a/Queue.m b/Queue.m index e6222a1..8d6954c 100644 --- a/Queue.m +++ b/Queue.m @@ -14,6 +14,8 @@ @implementation Queue +@synthesize itemsIn; +@synthesize itemsOut; @synthesize eventQueue; @synthesize queueCondition; @synthesize qProcessorThread; @@ -50,12 +52,6 @@ // ->don't want UI thread, etc to suffer [NSThread sleepForTimeInterval:5.0f]; - //VT object - VirusTotal* vtObject = nil; - - //grab VT object - vtObject = ((AppDelegate*)[[NSApplication sharedApplication] delegate]).virusTotalObj; - //for ever while(YES) { @@ -75,37 +71,30 @@ //get item off queue binary = [eventQueue dequeue]; - //sanity check - if(YES != [binary isKindOfClass:[Binary class]]) - { - //ignore - continue; - } - - //process - //->for now, just hash, etc - [binary generateDetailedInfo]; - - //when connected - // ->add item for VT processing - if(YES == ((AppDelegate*)[[NSApplication sharedApplication] delegate]).isConnected) - { - //add - [vtObject addItem:binary]; - } + //inc + itemsOut++; //unlock [self.queueCondition unlock]; - //pool + //generate hashes, etc + [binary generateDetailedInfo]; + + //when connected + // add item for VT processing + if(YES == isConnected) + { + //add + [virusTotal addItem:binary]; } + + } //pool }//foreverz process queue return; } - //add an object to the queue -(void)enqueue:(id)anObject { @@ -115,6 +104,9 @@ //add to queue [self.eventQueue enqueue:anObject]; + //inc + itemsIn++; + //signal [self.queueCondition signal]; @@ -124,10 +116,4 @@ return; } -//process binary --(void)processBinary:(Binary*)binary -{ - -} - @end diff --git a/SearchWindowController.m b/SearchWindowController.m index 594c6c3..0cfd106 100644 --- a/SearchWindowController.m +++ b/SearchWindowController.m @@ -85,7 +85,7 @@ { //not done? // ->make spinner keep spinning - if(ENUMERATION_STATE_COMPLETE != ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.state) + if(ENUMERATION_STATE_COMPLETE != taskEnumerator.state) { //(re)start [self.activityIndicator startAnimation:nil]; @@ -115,7 +115,7 @@ //still enumerating? // ->show the spinner - if(ENUMERATION_STATE_COMPLETE != ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.state) + if(ENUMERATION_STATE_COMPLETE != taskEnumerator.state) { //show self.activityIndicator.hidden = NO; @@ -163,7 +163,7 @@ }); //done? - if(ENUMERATION_STATE_COMPLETE == ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.state) + if(ENUMERATION_STATE_COMPLETE == taskEnumerator.state) { //stop spinner [self.activityIndicator stopAnimation:nil]; @@ -180,7 +180,7 @@ -(void)showEnumerationState { //set status - switch(((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.state) { + switch(taskEnumerator.state) { //tasks case ENUMERATION_STATE_TASKS: @@ -533,8 +533,20 @@ bail: //maks self.overlay.layer.masksToBounds = YES; - //set overlay's view color to gray - self.overlay.layer.backgroundColor = NSColor.grayColor.CGColor; + //dark mode + // set overlay to light + if(YES == isDarkMode()) + { + //set overlay's view color to gray + self.overlay.layer.backgroundColor = NSColor.lightGrayColor.CGColor; + } + //light mode + // set overlay to gray + else + { + //set to gray + self.overlay.layer.backgroundColor = NSColor.grayColor.CGColor; + } //make it semi-transparent self.overlay.alphaValue = 0.95; @@ -553,7 +565,7 @@ bail: //grab all tasks // make copy to avoid threading issues - allTasks = [((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.tasks copy]; + allTasks = [taskEnumerator.tasks copy]; //kick off filtering in background // will call back into to refresh UI when done diff --git a/Task.h b/Task.h index 9d21b3e..0e95827 100644 --- a/Task.h +++ b/Task.h @@ -91,6 +91,6 @@ struct dyld_image_info_32 { -(void)enumerateNetworking:(BOOL)shouldWait; //convert self to JSON string --(NSString*)toJSON; +-(NSString*)toJSON:(BOOL)detailed; @end diff --git a/Task.m b/Task.m index 22617e0..3b7d123 100644 --- a/Task.m +++ b/Task.m @@ -51,7 +51,7 @@ if(nil != self) { //grab existings binaries - existingBinaries = ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.executables; + existingBinaries = taskEnumerator.executables; //since root UID is zero // ->init UID to -1 @@ -111,7 +111,7 @@ //add to queue // ->this will process in background - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.binaryQueue enqueue:self.binary]; + [taskEnumerator.binaryQueue enqueue:self.binary]; //sync @synchronized(existingBinaries) @@ -362,7 +362,7 @@ bail: //add to queue // ->this will trigger background processing - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.binaryQueue enqueue:dylib]; + [taskEnumerator.binaryQueue enqueue:dylib]; //add to list of new dylibs // ->will allow for post processing @@ -492,7 +492,7 @@ bail: //reset existing files [self.files removeAllObjects]; - + //create/add all files for(NSMutableDictionary* fileDescriptor in fileDescriptors) { @@ -509,7 +509,7 @@ bail: [self.files addObject:file]; } } - + //sort by name self.files = [[self.files sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { @@ -630,7 +630,7 @@ bail: } //convert self to JSON string --(NSString*)toJSON +-(NSString*)toJSON:(BOOL)detailed { //json string NSString *json = nil; @@ -738,63 +738,75 @@ bail: //init VT detection ratio vtDetectionRatio = [NSString stringWithFormat:@"%lu/%lu", (unsigned long)[self.binary.vtInfo[VT_RESULTS_POSITIVES] unsignedIntegerValue], (unsigned long)[self.binary.vtInfo[VT_RESULTS_TOTAL] unsignedIntegerValue]]; - //sync - @synchronized(self.dylibs) + //detailed? + // generate full json + if(YES == detailed) { - //convert all dylibs and add - for(Binary* dylib in self.dylibs) + //sync + @synchronized(self.dylibs) { - //convert/add - [dylibsJSON appendFormat:@"{%@},", [dylib toJSON]]; + //convert all dylibs and add + for(Binary* dylib in self.dylibs) + { + //convert/add + [dylibsJSON appendFormat:@"{%@},", [dylib toJSON]]; + } } - } - - //remove last ',' - if(YES == [dylibsJSON hasSuffix:@","]) - { - //remove - [dylibsJSON deleteCharactersInRange:NSMakeRange([dylibsJSON length]-1, 1)]; - } - - //sync - @synchronized(self.files) - { - //convert all file and add - for(File* file in self.files) + + //remove last ',' + if(YES == [dylibsJSON hasSuffix:@","]) { - //convert/add - [filesJSON appendFormat:@"{%@},", [file toJSON]]; + //remove + [dylibsJSON deleteCharactersInRange:NSMakeRange([dylibsJSON length]-1, 1)]; } - } - - //remove last ',' - if(YES == [filesJSON hasSuffix:@","]) - { - //remove - [filesJSON deleteCharactersInRange:NSMakeRange([filesJSON length]-1, 1)]; - } - - //sync - @synchronized(self.connections) - { - //convert all connections and add - for(Connection* connection in self.connections) + + //sync + @synchronized(self.files) { - //convert/add - [connectionsJSON appendFormat:@"{%@},", [connection toJSON]]; + //convert all file and add + for(File* file in self.files) + { + //convert/add + [filesJSON appendFormat:@"{%@},", [file toJSON]]; + } } + + //remove last ',' + if(YES == [filesJSON hasSuffix:@","]) + { + //remove + [filesJSON deleteCharactersInRange:NSMakeRange([filesJSON length]-1, 1)]; + } + + //sync + @synchronized(self.connections) + { + //convert all connections and add + for(Connection* connection in self.connections) + { + //convert/add + [connectionsJSON appendFormat:@"{%@},", [connection toJSON]]; + } + } + + //remove last ',' + if(YES == [connectionsJSON hasSuffix:@","]) + { + //remove + [connectionsJSON deleteCharactersInRange:NSMakeRange([connectionsJSON length]-1, 1)]; + } + + //init json + json = [NSString stringWithFormat:@"\"name\": \"%@\", \"path\": \"%@\", \"pid\": \"%@\", \"command line\": \"%@\", \"hashes\": %@, \"signature(s)\": %@, \"VT detection\": \"%@\", \"encrypted\": %d, \"packed\": %d, \"not found\": %d, \"dylibs\": [%@], \"files\": [%@], \"connections\": [%@]", self.binary.name, self.binary.path, self.pid, taskCommandLine, fileHashes, fileSigs, vtDetectionRatio, self.binary.isEncrypted, self.binary.isPacked, self.binary.notFound, dylibsJSON, filesJSON, connectionsJSON]; } - //remove last ',' - if(YES == [connectionsJSON hasSuffix:@","]) + //basic + else { - //remove - [connectionsJSON deleteCharactersInRange:NSMakeRange([connectionsJSON length]-1, 1)]; + //init json + json = [NSString stringWithFormat:@"\"name\": \"%@\", \"path\": \"%@\", \"pid\": \"%@\", \"command line\": \"%@\", \"hashes\": %@, \"signature(s)\": %@, \"VT detection\": \"%@\", \"encrypted\": %d, \"packed\": %d, \"not found\": %d", self.binary.name, self.binary.path, self.pid, taskCommandLine, fileHashes, fileSigs, vtDetectionRatio, self.binary.isEncrypted, self.binary.isPacked, self.binary.notFound]; } - //init json - json = [NSString stringWithFormat:@"\"name\": \"%@\", \"path\": \"%@\", \"pid\": \"%@\", \"command line\": \"%@\", \"hashes\": %@, \"signature(s)\": %@, \"VT detection\": \"%@\", \"encrypted\": %d, \"packed\": %d, \"not found\": %d, \"dylibs\": [%@], \"files\": [%@], \"connections\": [%@]", self.binary.name, self.binary.path, self.pid, taskCommandLine, fileHashes, fileSigs, vtDetectionRatio, self.binary.isEncrypted, self.binary.isPacked, self.binary.notFound, dylibsJSON, filesJSON, connectionsJSON]; - return json; } diff --git a/TaskEnumerator.h b/TaskEnumerator.h index 5d61894..5940905 100644 --- a/TaskEnumerator.h +++ b/TaskEnumerator.h @@ -6,12 +6,21 @@ // // +#import "Task.h" #import "Queue.h" +#import "Consts.h" +#import "Signing.h" +#import "Utilities.h" +#import "Connection.h" #import "3rdParty/OrderedDictionary.h" +#import +#import +#import +#import +#import #import - @interface TaskEnumerator : NSObject { @@ -28,6 +37,9 @@ //all dylibs @property(nonatomic, retain)NSMutableDictionary* dylibs; +//flagged items +@property(nonatomic, retain)NSMutableArray* flaggedItems; + //queue // ->contains binaries that should be processed @property (nonatomic, retain)Queue* binaryQueue; diff --git a/TaskEnumerator.m b/TaskEnumerator.m index 836eb93..b1e25b1 100644 --- a/TaskEnumerator.m +++ b/TaskEnumerator.m @@ -6,17 +6,6 @@ // // -#import -#import -#import -#import -#import - -#import "Task.h" -#import "Consts.h" -#import "Signing.h" -#import "Utilities.h" -#import "Connection.h" #import "AppDelegate.h" #import "TaskEnumerator.h" @@ -27,6 +16,7 @@ @synthesize dylibs; @synthesize binaryQueue; @synthesize executables; +@synthesize flaggedItems; //init -(id)init @@ -44,6 +34,9 @@ //alloc dylibs dictionary dylibs = [NSMutableDictionary dictionary]; + //alloc flagged items + flaggedItems = [NSMutableArray array]; + //init binary processing queue binaryQueue = [[Queue alloc] init]; } @@ -53,7 +46,7 @@ //enumerate all tasks -// ->calls back into app delegate to update task (top) table when pau +// calls back into app delegate to update task (top) table when pau -(void)enumerateTasks { //(new) task item @@ -71,10 +64,6 @@ //set state self.state = ENUMERATION_STATE_TASKS; - //determine if network is connected - // ->sets 'isConnected' flag - ((AppDelegate*)[[NSApplication sharedApplication] delegate]).isConnected = isNetworkConnected(); - //get all tasks // ->pids and binary obj with just path/name newTasks = [self getAllTasks]; @@ -138,13 +127,18 @@ //reload task table [((AppDelegate*)[[NSApplication sharedApplication] delegate]) reloadTaskTable]; - //call on main thread - dispatch_sync(dispatch_get_main_queue(), ^{ + //reload bottom pain + // call on main thread + if(YES != [NSThread isMainThread]) + { + //main thread + dispatch_sync(dispatch_get_main_queue(), ^{ - //reload bottom pane - [((AppDelegate*)[[NSApplication sharedApplication] delegate]) selectBottomPaneContent:nil]; - - }); + //reload bottom pane + [((AppDelegate*)[[NSApplication sharedApplication] delegate]) selectBottomPaneContent:nil]; + + }); + } //for new tasks // now generate signing info/encryption check/packer check @@ -226,7 +220,7 @@ //nap [NSThread sleepForTimeInterval:0.01]; } - + //set state self.state = ENUMERATION_STATE_FILES; @@ -234,7 +228,6 @@ count = 0; //begin file enumeration - // ->for search view for(NSNumber* key in newTasks) { //get task @@ -264,7 +257,7 @@ //nap [NSThread sleepForTimeInterval:0.01]; } - + //set state self.state = ENUMERATION_STATE_NETWORK; @@ -272,7 +265,6 @@ count = 0; //begin network enumeration - // ->for search view for(NSNumber* key in newTasks) { //get task @@ -544,10 +536,10 @@ bail: } //sync to remove from all executables - @synchronized(((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.executables) + @synchronized(taskEnumerator.executables) { //remove dead executables - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.executables removeObjectForKey:deadTask.binary.path]; + [taskEnumerator.executables removeObjectForKey:deadTask.binary.path]; } //get parent @@ -576,7 +568,7 @@ bail: for(Binary* dylib in deadTask.dylibs) { //skip dylibs that aren't flagged - if(YES != [((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems containsObject:dylib]) + if(YES != [taskEnumerator.flaggedItems containsObject:dylib]) { //skip continue; @@ -596,15 +588,15 @@ bail: //dylib is flagged and only hosted in dead task // ->remove it from flaggedItems - @synchronized(((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems) + @synchronized(taskEnumerator.flaggedItems) { //remove - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems removeObject:dylib]; + [taskEnumerator.flaggedItems removeObject:dylib]; } } //also remove task if its flagged and only instance - if(YES == [((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems containsObject:deadTask.binary]) + if(YES == [taskEnumerator.flaggedItems containsObject:deadTask.binary]) { //get number of task instances // ->might be more (flagged) instances that are still alive @@ -625,17 +617,17 @@ bail: if(1 == taskInstances) { //sync and remove - @synchronized(((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems) + @synchronized(taskEnumerator.flaggedItems) { //remove - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems removeObject:deadTask.binary]; + [taskEnumerator.flaggedItems removeObject:deadTask.binary]; } } } //when there are no flagged items // ->(re)set flagged icon to black - if(0 == ((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems.count) + if(0 == taskEnumerator.flaggedItems.count) { //set main image [((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedButton setImage:[NSImage imageNamed:@"flagged"]]; @@ -706,7 +698,6 @@ bail: return matchingTasks; } - //get all tasks a dylib/file is loaded into -(NSMutableArray*)loadedIn:(id)item { @@ -771,8 +762,8 @@ bail: if(YES == isDylib) { //sync - //@synchronized(task.dylibs) - //{ + @synchronized(task.dylibs) + { //check if dylib is loaded in task for(Binary* taskDylib in task.dylibs) { @@ -787,7 +778,7 @@ bail: } } - //}//sync + }//sync }//dylibs @@ -795,8 +786,8 @@ bail: else if(YES == isFile) { //sync - //@synchronized(task.files) - //{ + @synchronized(task.files) + { //check if file is loaded in task for(File* taskFile in task.files) { @@ -810,7 +801,7 @@ bail: break; } } - //}//sync + }//sync }//files @@ -818,8 +809,8 @@ bail: else if(YES == isConnection) { //sync - //@synchronized(task.connections) - //{ + @synchronized(task.connections) + { //check if connection is 'in' task for(Connection* taskConnection in task.connections) { @@ -834,12 +825,10 @@ bail: break; } } - //}//sync + }//sync }//connections - - - + }//all tasks }//sync @@ -850,5 +839,4 @@ bail: return hostTasks; } - @end diff --git a/TaskExplorer-Info.plist b/TaskExplorer-Info.plist index 30df5da..8d20bfe 100755 --- a/TaskExplorer-Info.plist +++ b/TaskExplorer-Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.7.0 + 2.0.0 CFBundleSignature ???? CFBundleVersion - 1.7.0 + 2.0.0 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSHumanReadableCopyright @@ -30,5 +30,7 @@ MainMenu NSPrincipalClass NSApplicationKeyEvents + LSUIElement + diff --git a/TaskExplorer.xcodeproj/project.pbxproj b/TaskExplorer.xcodeproj/project.pbxproj index b5991b8..9814197 100755 --- a/TaskExplorer.xcodeproj/project.pbxproj +++ b/TaskExplorer.xcodeproj/project.pbxproj @@ -222,6 +222,7 @@ CD1508DE21BB13980081F1AF /* Signing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Signing.h; sourceTree = SOURCE_ROOT; }; CD1D14EB21B7A5DB00FF7F4B /* Sentry.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sentry.framework; path = Carthage/Build/Mac/Sentry.framework; sourceTree = ""; }; CD1D14F221B8F2FC00FF7F4B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = SOURCE_ROOT; }; + CD24FE7621C6FC3D00900B61 /* main.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = main.h; sourceTree = SOURCE_ROOT; }; CD3F4CE61AF5CF68002A2647 /* TaskEnumerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TaskEnumerator.m; sourceTree = SOURCE_ROOT; }; CD3F4CE71AF5CF68002A2647 /* TaskEnumerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TaskEnumerator.h; sourceTree = SOURCE_ROOT; }; CD3F4CE91AF6D948002A2647 /* OrderedDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OrderedDictionary.h; path = 3rdParty/OrderedDictionary.h; sourceTree = ""; }; @@ -479,6 +480,7 @@ CDA81D581A95B4B4009790E2 /* TaskExplorer-Info.plist */, CDA81D591A95B4B4009790E2 /* TaskExplorer-Prefix.pch */, CDA81D5A1A95B4B4009790E2 /* main.m */, + CD24FE7621C6FC3D00900B61 /* main.h */, ); name = "Supporting Files"; sourceTree = ""; diff --git a/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrick.xcuserdatad/UserInterfaceState.xcuserstate b/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrick.xcuserdatad/UserInterfaceState.xcuserstate index ae26656..dee3b90 100644 Binary files a/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrick.xcuserdatad/UserInterfaceState.xcuserstate and b/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrick.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/TaskExplorer.xcodeproj/xcuserdata/patrick.xcuserdatad/xcschemes/TaskExplorer.xcscheme b/TaskExplorer.xcodeproj/xcuserdata/patrick.xcuserdatad/xcschemes/TaskExplorer.xcscheme index 8389691..a71d917 100644 --- a/TaskExplorer.xcodeproj/xcuserdata/patrick.xcuserdatad/xcschemes/TaskExplorer.xcscheme +++ b/TaskExplorer.xcodeproj/xcuserdata/patrick.xcuserdatad/xcschemes/TaskExplorer.xcscheme @@ -46,6 +46,12 @@ ReferencedContainer = "container:TaskExplorer.xcodeproj"> + + + + make sure there is table item for row @@ -325,7 +325,7 @@ bail: NSUInteger taskIndex = 0; //grab tasks - tasks = ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.tasks; + tasks = taskEnumerator.tasks; //get task selectedTask = [self taskForRow:nil]; @@ -427,7 +427,7 @@ bail: Task* task = nil; //grab tasks - tasks = ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.tasks; + tasks = taskEnumerator.tasks; //use sender if provided if(nil != sender) @@ -658,7 +658,7 @@ bail: OrderedDictionary* tasks = nil; //grab tasks - tasks = ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.tasks; + tasks = taskEnumerator.tasks; //number of children NSUInteger numberOfChildren = 0; @@ -708,7 +708,7 @@ bail: Task* task = nil; //grab all tasks - tasks = ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.tasks; + tasks = taskEnumerator.tasks; //root item if(nil == item) @@ -797,7 +797,7 @@ bail: } //grab tasks - tasks = ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.tasks; + tasks = taskEnumerator.tasks; //get index of newly selected row newlySelectedRow = [self.itemView selectedRow]; @@ -834,7 +834,7 @@ bail: ((kkRowCell*)selectedView).color = nil; //remove task from task emumerator - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator removeTask:task]; + [taskEnumerator removeTask:task]; //when filtering // ->remove deceased task from filter tasks diff --git a/UI/FlatView.xib b/UI/FlatView.xib index a731cf1..9b88315 100755 --- a/UI/FlatView.xib +++ b/UI/FlatView.xib @@ -1,10 +1,8 @@ - + - - - + @@ -75,7 +73,7 @@ - + + @@ -52,6 +55,7 @@ + @@ -60,6 +64,7 @@ + @@ -68,6 +73,7 @@ + @@ -76,6 +82,7 @@ + @@ -84,6 +91,7 @@ + @@ -92,11 +100,13 @@ + diff --git a/Utilities.h b/Utilities.h index 5f69ade..24877e2 100644 --- a/Utilities.h +++ b/Utilities.h @@ -15,6 +15,9 @@ /* FUNCTIONS */ +//disable std err +void disableSTDERR(void); + //loads a framework // note: assumes it is in 'Framework' dir NSBundle* loadFramework(NSString* name); @@ -84,8 +87,15 @@ BOOL Is32Bit(pid_t targetPID); // ->based on http://lapcatsoftware.com/articles/detect-app-translocation.html NSURL* getUnTranslocatedURL(); +//give a list of paths +// convert any `~` to all or current user +NSMutableArray* expandPaths(const __strong NSString* const paths[], int count); + //check if (full) dark mode // meaning, Mojave+ and dark mode enabled BOOL isDarkMode(); +//bring an app to foreground (to get an icon in the dock) or background +void transformProcess(ProcessApplicationTransformState location); + #endif diff --git a/Utilities.m b/Utilities.m index 0049820..bc92873 100644 --- a/Utilities.m +++ b/Utilities.m @@ -20,8 +20,28 @@ #import #import #import +#import +#import #import +//disable std err +void disableSTDERR() +{ + //file handle + int devNull = -1; + + //open /dev/null + devNull = open("/dev/null", O_RDWR); + + //dup + dup2(devNull, STDERR_FILENO); + + //close + close(devNull); + + return; +} + //init crash reporting void initCrashReporting() { @@ -1182,7 +1202,128 @@ bail: } return untranslocatedURL; +} + +//get all user +// includes name/home directory +NSMutableDictionary* allUsers() +{ + //users + NSMutableDictionary* users = nil; + //query + CSIdentityQueryRef query = nil; + + //query results + CFArrayRef results = NULL; + + //error + CFErrorRef error = NULL; + + //identiry + CBIdentity* identity = NULL; + + //alloc dictionary + users = [NSMutableDictionary dictionary]; + + //init query + query = CSIdentityQueryCreate(NULL, kCSIdentityClassUser, CSGetLocalIdentityAuthority()); + + //exec query + if(true != CSIdentityQueryExecute(query, 0, &error)) + { + //bail + goto bail; + } + + //grab results + results = CSIdentityQueryCopyResults(query); + + //process all results + // add user and home directory + for (int i = 0; i < CFArrayGetCount(results); ++i) + { + //grab identity + identity = [CBIdentity identityWithCSIdentity:(CSIdentityRef)CFArrayGetValueAtIndex(results, i)]; + + //add user + users[identity.UUIDString] = @{USER_NAME:identity.posixName, USER_DIRECTORY:NSHomeDirectoryForUser(identity.posixName)}; + } + +bail: + + //release results + if(NULL != results) + { + //release + CFRelease(results); + } + + //release query + if(NULL != query) + { + //release + CFRelease(query); + } + + return users; +} + +//give a list of paths +// convert any `~` to all or current user +NSMutableArray* expandPaths(const __strong NSString* const paths[], int count) +{ + //expanded paths + NSMutableArray* expandedPaths = nil; + + //(current) path + const NSString* path = nil; + + //all users + NSMutableDictionary* users = nil; + + //grab all users + users = allUsers(); + + //alloc list + expandedPaths = [NSMutableArray array]; + + //iterate/expand + for(NSInteger i = 0; i < count; i++) + { + //grab path + path = paths[i]; + + //no `~`? + // just add and continue + if(YES != [path hasPrefix:@"~"]) + { + //add as is + [expandedPaths addObject:path]; + + //next + continue; + } + + //handle '~' case + // root? add each user + if(0 == geteuid()) + { + //add each user + for(NSString* user in users) + { + [expandedPaths addObject:[users[user][USER_DIRECTORY] stringByAppendingPathComponent:[path substringFromIndex:1]]]; + } + } + //otherwise + // just convert to current user + else + { + [expandedPaths addObject:[path stringByExpandingTildeInPath]]; + } + } + + return expandedPaths; } //check if (full) dark mode @@ -1215,6 +1356,26 @@ bail: return darkMode; } +//bring an app to foreground (to get an icon in the dock) or background +void transformProcess(ProcessApplicationTransformState location) +{ + //process serial no + ProcessSerialNumber processSerialNo; + + //init process stuct + // ->high to 0 + processSerialNo.highLongOfPSN = 0; + + //init process stuct + // ->low to self + processSerialNo.lowLongOfPSN = kCurrentProcess; + + //transform to foreground + TransformProcessType(&processSerialNo, location); + + return; +} + diff --git a/VTButton.m b/VTButton.m index 0a78e2a..a1220a7 100644 --- a/VTButton.m +++ b/VTButton.m @@ -42,7 +42,7 @@ } //automatically invoked when mouse-down occurs -// ->set color to light gray or light red +// ->set color to default or red -(void)mouseDown:(NSEvent *)theEvent; { //mouse down/over color @@ -60,11 +60,10 @@ color = [NSColor colorWithCalibratedRed:(255/255.0f) green:(1.0/255.0f) blue:(1.0/255.0f) alpha:0.5]; } //non-flagged files - //gray else { - //gray - color = NSColor.grayColor; + //default + color = NSColor.controlTextColor; } //set string @@ -74,7 +73,7 @@ } //automatically invoked when mouse-up occurs -// ->reset color to gray or red and trigger mouse click logic (if necessary) +// ->reset color to default or red and trigger mouse click logic (if necessary) -(void)mouseUp:(NSEvent *)theEvent; { //mouse up color @@ -135,8 +134,8 @@ // set (back) to default else { - //gray - color = NSColor.grayColor; + //default + color = NSColor.controlTextColor; } //set string @@ -156,7 +155,7 @@ self.mouseExit = YES; //check if mouse is down - // ->set color to gray/lightish red + // ->set color to default/red if(YES == self.mouseDown) { //flagged files @@ -168,11 +167,10 @@ color = [NSColor colorWithCalibratedRed:(255/255.0f) green:(1.0/255.0f) blue:(1.0/255.0f) alpha:0.66]; } //non-flagged files - // ->just black else { - //gray - color = NSColor.grayColor; + //default + color = NSColor.controlTextColor; } } //mouse is up @@ -191,7 +189,7 @@ // set (back) to default else { - //gray + //default color = NSColor.controlTextColor; } } diff --git a/VTInfoWindowController.m b/VTInfoWindowController.m index ef522e6..4b77b77 100644 --- a/VTInfoWindowController.m +++ b/VTInfoWindowController.m @@ -135,7 +135,7 @@ makeTextViewHyperlink(self.analysisURL, [NSURL URLWithString:self.item.vtInfo[VT_RESULTS_URL]]); //set 'submit' button text to 'rescan' - self.submitButton.title = @"rescan?"; + self.submitButton.title = @"Rescan?"; } //unknown file else @@ -236,9 +236,21 @@ //pre-req [self.overlayView setWantsLayer:YES]; - //set overlay's view color to white - self.overlayView.layer.backgroundColor = [NSColor whiteColor].CGColor; - + //dark mode + // set overlay to light + if(YES == isDarkMode()) + { + //set overlay's view color to gray + self.overlayView.layer.backgroundColor = NSColor.lightGrayColor.CGColor; + } + //light mode + // set overlay to gray + else + { + //set to gray + self.overlayView.layer.backgroundColor = NSColor.grayColor.CGColor; + } + //make it semi-transparent self.overlayView.alphaValue = 0.85; @@ -252,7 +264,7 @@ [self.progressIndicator startAnimation:nil]; //rescan file? - if(YES == [((NSButton*)sender).title isEqualToString:@"rescan?"]) + if(YES == [((NSButton*)sender).title isEqualToString:@"Rescan?"]) { //set status msg [self.statusMsg setStringValue:[NSString stringWithFormat:@"submitting re-scan request for %@", self.item.name]]; diff --git a/VirusTotal.h b/VirusTotal.h index 0add6d2..96c7f1a 100644 --- a/VirusTotal.h +++ b/VirusTotal.h @@ -19,6 +19,8 @@ //array for (up to 25) items @property(nonatomic, retain)NSMutableArray* items; +//array for all threads +@property(nonatomic, retain)NSMutableArray* vtThreads; /* METHODS */ @@ -26,10 +28,6 @@ // ->will query VT when 25 items are hit -(void)addItem:(Binary*)binary; -//thread function -// ->runs in the background to get virus total info about a plugin's items -//-(void)getInfo:(PluginBase*)plugin; - //make the (POST)query to VT -(NSDictionary*)postRequest:(NSURL*)url parameters:(id)params; diff --git a/VirusTotal.m b/VirusTotal.m index ddc8ea4..85309b6 100644 --- a/VirusTotal.m +++ b/VirusTotal.m @@ -17,6 +17,7 @@ @implementation VirusTotal @synthesize items; +@synthesize vtThreads; //init -(id)init @@ -28,8 +29,11 @@ //alloc array for items items = [NSMutableArray array]; + //init array for virus total threads + vtThreads = [NSMutableArray array]; + //kick of thread to watch/flush queue - // ->will flush if item not processed in 30 seconds + // ->will flush if item not processed in 3 seconds [NSThread detachNewThreadSelector:@selector(queueFlusher) toTarget:self withObject:nil]; } @@ -98,6 +102,9 @@ //items to process NSMutableArray* vtItems = nil; + //virus total thread + NSThread* virusTotalThread = nil; + //sync @synchronized(self.items) { @@ -108,10 +115,21 @@ if(VT_MAX_QUERY_COUNT == self.items.count) { //make copy - vtItems = [NSMutableArray arrayWithArray:self.items]; - - //kick of thread to make a query to VT - [NSThread detachNewThreadSelector:@selector(queryVT:) toTarget:self withObject:vtItems]; + vtItems = [NSMutableArray arrayWithArray:self.items]; + + //alloc thread + // ->will query virus total to get info about all detected items + virusTotalThread = [[NSThread alloc] initWithTarget:self selector:@selector(queryVT:) object:vtItems]; + + //start thread + [virusTotalThread start]; + + //sync + @synchronized(self.vtThreads) + { + //save it into array + [self.vtThreads addObject:virusTotalThread]; + } //remove all items [self.items removeAllObjects]; @@ -226,22 +244,29 @@ //save flagged item if(0 != [results[VT_RESULTS_POSITIVES] unsignedIntegerValue]) { - //save - [((AppDelegate*)[[NSApplication sharedApplication] delegate]) saveFlaggedBinary:item]; + //sync to check/add + @synchronized(taskEnumerator.flaggedItems) + { + if(YES != [taskEnumerator.flaggedItems containsObject:item]) + { + //save + [taskEnumerator.flaggedItems addObject:item]; + } + } } //for non-flagged items - // ->remove from list, if they were previously flagged + // remove from list, if it was previously flagged else { - //check if previously flagged - // ->then remove - if(YES == [((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems containsObject:item]) + //sync to check/remove + @synchronized(taskEnumerator.flaggedItems) { - //sync to remove - @synchronized(((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems) + //check if previously flagged + // ...if so, then remove + if(YES == [taskEnumerator.flaggedItems containsObject:item]) { //remove - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems removeObject:item]; + [taskEnumerator.flaggedItems removeObject:item]; } } } @@ -300,9 +325,6 @@ postData = [NSJSONSerialization dataWithJSONObject:params options:kNilOptions error:nil]; if(nil == postData) { - //err msg - syslog(LOG_ERR, "OBJECTIVE-SEE ERROR: failed to convert request %s to JSON", [[postData description] UTF8String]); - //bail goto bail; } @@ -354,9 +376,6 @@ //bail on any exceptions @catch (NSException *exception) { - //err msg - syslog(LOG_ERR, "OBJECTIVE-SEE ERROR: converting response %s to JSON threw %s", [[vtData description] UTF8String], [[exception description] UTF8String]); - //bail goto bail; } @@ -364,9 +383,6 @@ //sanity check if(nil == results) { - //err msg - syslog(LOG_ERR, "OBJECTIVE-SEE ERROR: failed to convert response %s to JSON", [[vtData description] UTF8String]); - //bail goto bail; } @@ -513,13 +529,11 @@ bail: goto bail; } -//bail bail: return results; } - //submit a rescan request -(NSDictionary*)reScan:(Binary*)item { @@ -551,7 +565,6 @@ bail: goto bail; } -//bail bail: return result; @@ -563,7 +576,7 @@ bail: { //queried binary obj Binary* queriedItem = nil; - + //process all results // ->save VT result dictionary into File obj for(NSDictionary* result in results[VT_RESULTS]) @@ -571,8 +584,6 @@ bail: //extract ('match') queried item // ->VT gives us back a hash queriedItem = queriedItems[result[@"hash"]]; - - //sanity check if(nil == queriedItem) { //skip @@ -582,11 +593,16 @@ bail: //save VT results into item queriedItem.vtInfo = result; - //save flagged item - if(0 != [result[VT_RESULTS_POSITIVES] unsignedIntegerValue]) + //sync to check/add + @synchronized(taskEnumerator.flaggedItems) { - //save - [((AppDelegate*)[[NSApplication sharedApplication] delegate]) saveFlaggedBinary:queriedItem]; + //save flagged item + if( (0 != [result[VT_RESULTS_POSITIVES] unsignedIntegerValue]) && + (YES != [taskEnumerator.flaggedItems containsObject:queriedItem]) ) + { + //save + [taskEnumerator.flaggedItems addObject:queriedItem]; + } } //call up into app delegate to smartly reload diff --git a/main.h b/main.h new file mode 100644 index 0000000..a5d203e --- /dev/null +++ b/main.h @@ -0,0 +1,53 @@ +// +// main.h +// TaskExplorer +// +// Created by Patrick Wardle on 12/16/18. +// Copyright © 2018 Lucas Derraugh. All rights reserved. +// + +#ifndef main_h +#define main_h + +#import "Consts.h" +#import "Filter.h" +#import "Utilities.h" +#import "VirusTotal.h" +#import "AppDelegate.h" + +#import "TaskEnumerator.h" + +#import + +//(privacy) protected directories +NSString * const PROTECTED_DIRECTORIES[] = {@"~/Library/Application Support/AddressBook", @"~/Library/Calendars", @"~/Pictures", @"~/Library/Mail", @"~/Library/Messages", @"~/Library/Safari", @"~/Library/Cookies", @"~/Library/HomeKit", @"~/Library/IdentityServices", @"~/Library/Metadata/CoreSpotlight", @"~/Library/PersonalizationPortrait", @"~/Library/Suggestions"}; + +/* GLOBALS */ + +//task enumerator obj +TaskEnumerator* taskEnumerator = nil; + +//virustotal obj +VirusTotal* virusTotal = nil; + +//network connected flag +BOOL isConnected = NO; + +//(privacy) protected directories +NSArray* protectedDirectories = nil; + +/* FUNCTIONS */ + +//print usage +void usage(void); + +//perform a cmdline enumeration +void cmdlineExplore(void); + +//block until vt queries are done +void completeVTQuery(void); + +//pretty print JSON +void prettyPrintJSON(NSString* output); + +#endif /* main_h */ diff --git a/main.m b/main.m index 3ab672c..ec9a248 100755 --- a/main.m +++ b/main.m @@ -6,19 +6,22 @@ // Copyright (c) 2015 Objective-See. All rights reserved. // -#import "Consts.h" -#import "Utilities.h" - -#import - +#import "main.h" //main interface -// ->contains extra logic to handle app translocation +// 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; @@ -29,8 +32,11 @@ int main(int argc, char *argv[]) //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 + // use 'open' since allows two instances of app to be run execTask(OPEN, @[@"-n", @"-a", untranslocatedURL.path], NO); //happy @@ -40,17 +46,348 @@ int main(int argc, char *argv[]) goto bail; } - //app isn't translocated - // ->can just run app as is + //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; + } + + //scan + cmdlineExplore(); + + //happy + status = 0; + + //done + goto bail; + } + + //otherwise + // just kick off app for UI instance else { //invoke app's main status = NSApplicationMain(argc, (const char **)argv); } -//bail 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(" -skipVT do not query VirusTotal (when '-explore' is specified)\n"); + printf(" -full for each task; include dylibs, files, & network connections\n\n"); + + return; +} + +//perform a cmdline enumeration of all things +void cmdlineExplore() +{ + //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; + + //init filter obj + filter = [[Filter alloc] init]; + + //init task enumerator object + taskEnumerator = [[TaskEnumerator alloc] init]; + + //set flag + // skip virus total? + skipVirusTotal = [[[NSProcessInfo processInfo] arguments] containsObject:@"-skipVT"]; + + //virus total? + if(YES != skipVirusTotal) + { + //init virus total object + virusTotal = [[VirusTotal alloc] init]; + } + + //be nice + nice(15); + + //enumerate all tasks/dylibs/files/etc + [taskEnumerator enumerateTasks]; + + //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 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 = [[[NSProcessInfo processInfo] arguments] containsObject:@"-apple"]; + + //set flag + // pretty print json? + prettyPrint = [[[NSProcessInfo processInfo] arguments] containsObject:@"-pretty"]; + + //set flag + // full output? + detailed = [[[NSProcessInfo processInfo] arguments] containsObject:@"-detailed"]; + + //alloc output JSON + output = [NSMutableString string]; + + //only flagged items? + if(YES == [[[NSProcessInfo processInfo] 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:@"]}"]; + } + + else + { + //start JSON + [output appendString:@"{\"tasks\":["]; + + //get tasks + for(NSNumber* taskPid in taskEnumerator.tasks) + { + //skip apple? + if( (YES != includeApple) && + (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? + // add separate array of dylibs + if(YES != detailed) + { + //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); + } + + 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; +} diff --git a/remoteTaskService/Info.plist b/remoteTaskService/Info.plist index 18c6cb7..72f8bbc 100644 --- a/remoteTaskService/Info.plist +++ b/remoteTaskService/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.7.0 + 2.0.0 CFBundleSignature ???? CFBundleVersion - 1.7.0 + 2.0.0 NSHumanReadableCopyright Copyright © 2018 Objective-See, LLC. All rights reserved. XPCService