diff --git a/AboutWindowController.m b/AboutWindowController.m index 526bbeb..01b3cb9 100644 --- a/AboutWindowController.m +++ b/AboutWindowController.m @@ -1,6 +1,6 @@ // // PrefsWindowController.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/6/15. // Copyright (c) 2015 Objective-See, LLC. All rights reserved. diff --git a/AppDelegate.h b/AppDelegate.h index 35f5d91..4dfd3bd 100755 --- a/AppDelegate.h +++ b/AppDelegate.h @@ -1,6 +1,6 @@ // // AppDelegate.h -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle // Copyright (c) 2015 Objective-See. All rights reserved. @@ -19,6 +19,7 @@ #import "ResultsWindowController.h" #import "RequestRootWindowController.h" #import "SearchWindowController.h" +#import "CustomTextField.h" #import "Task.h" @@ -163,6 +164,17 @@ //flagged items @property(nonatomic, retain) NSMutableArray* flaggedItems; +//array of autocomplete keywords +//@property NSMutableArray *builtInKeywords; + +@property BOOL completePosting; +@property BOOL commandHandling; + +//custom search field for tasks +@property CustomTextField* customTasksFilter; + +//custom search field for items +@property CustomTextField* customItemsFilter; /* METHODS */ @@ -173,7 +185,8 @@ //init (setup) XPC connection -(BOOL)initXPC; -- (IBAction)switchView:(id)sender; +//switch between flat/tree view +-(IBAction)switchView:(id)sender; //init tracking areas for buttons // ->provide mouse over effects @@ -232,4 +245,12 @@ // ->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; + +//code to complete filtering/search +// ->reload table/scroll to top etc +-(void)finalizeFiltration:(NSUInteger)pane; + @end diff --git a/AppDelegate.m b/AppDelegate.m index daefc65..d86ce48 100755 --- a/AppDelegate.m +++ b/AppDelegate.m @@ -1,6 +1,6 @@ // // AppDelegate.m -// KnockKnock +// TaskExplorer // #import "Consts.h" @@ -21,13 +21,18 @@ //TODO: autolayout vertically -//TODO: filter VT results -//TODO: # autocomplete -//TODO: keyboard shortcuts +//TODO: filter VT results - HUH? +//TODO: # autocomplete - DONE + +//TODO: keyboard shortcuts - DONE! // see: https://mail.google.com/mail/u/0/#inbox/14eeb163d4dd2852 //TODO: show 'from where' via quarantine attrz //TODO: show user (after pid): -> (pid, user)? -//TODO: check if VT can be reached! if not, error? or don't show '0 VT results detected' etc... + +//TODO: syncronize filtered tasks - DONE! + +//TODO: add files/dylibs/connections to save - DONE! +//TODO: sync while saving //TODO: JavaW (iWorm) dylibs... @@ -36,34 +41,35 @@ @implementation AppDelegate + +@synthesize topPane; @synthesize filterObj; @synthesize startTime; @synthesize vtThreads; -@synthesize saveButton; -@synthesize isConnected; -@synthesize virusTotalObj; -@synthesize taskTableController; -@synthesize aboutWindowController; -@synthesize prefsWindowController; -@synthesize resultsWindowController; @synthesize bottomPane; -@synthesize bottomViewController; +@synthesize saveButton; @synthesize currentTask; -@synthesize requestRootWindowController; -@synthesize taskViewFormat; -@synthesize flagItemsWindowController; -@synthesize searchWindowController; - -@synthesize scannerThread; -@synthesize progressIndicator; - -@synthesize topPane; -@synthesize taskEnumerator; -@synthesize viewSelector; -@synthesize searchButton; -@synthesize xpcConnection; +@synthesize isConnected; @synthesize flaggedItems; - +@synthesize searchButton; +@synthesize viewSelector; +@synthesize scannerThread; +@synthesize virusTotalObj; +@synthesize xpcConnection; +@synthesize taskEnumerator; +@synthesize taskViewFormat; +@synthesize commandHandling; +@synthesize completePosting; +@synthesize customItemsFilter; +@synthesize customTasksFilter; +@synthesize progressIndicator; +@synthesize taskTableController; +@synthesize bottomViewController; +@synthesize aboutWindowController; +@synthesize searchWindowController; +@synthesize resultsWindowController; +@synthesize flagItemsWindowController; +@synthesize requestRootWindowController; //center window // ->also make front @@ -87,7 +93,9 @@ { //first thing... // ->install exception handlers! - installExceptionHandlers(); + + //TODO: CHANGE B4 RELEASE!! + //installExceptionHandlers(); //init virus total object virusTotalObj = [[VirusTotal alloc] init]; @@ -98,6 +106,18 @@ //alloc flagged items flaggedItems = [NSMutableArray array]; + //alloc/init custom search field for tasks + customTasksFilter = [[CustomTextField alloc] init]; + + //alloc/init custom search field for items + customItemsFilter = [[CustomTextField alloc] init]; + + //set field editor for tasks + [self.customTasksFilter setFieldEditor:YES]; + + //set field editor for items + [self.customItemsFilter setFieldEditor:YES]; + //set start time self.startTime = [NSDate timeIntervalSinceReferenceDate]; @@ -117,6 +137,9 @@ exit(0); } + //register for hotkey presses + [self registerKeypressHandler]; + //check if authenticated // ->display authentication request if needed if(YES != [self isAuthenticated]) @@ -165,9 +188,146 @@ // ->ensures our 'windowWillClose' method, which has logic to fully exit app self.window.delegate = self; + /* + //init list of keyword strings for our type completion dropdown list in NSSearchField + self.builtInKeywords = [NSMutableArray array]; + + //iterate over all const keywords + // ->add to array + for(NSUInteger i=0; iif not main window + if(self.window != [[NSApplication sharedApplication] keyWindow]) + { + //close window + [[[NSApplication sharedApplication] keyWindow] close]; + + //set flag + wasHandled = YES; + } + + break; + + + default: + break; + } + +//bail +bail: + + //nil out event if it was handled + if(YES == wasHandled) + { + event = nil; + } + + // Return the event, a new event, or, to stop + // the event from being dispatched, nil + return event; + + + +} + //complete a few inits // ->then invoke helper method to start enum'ing task (in bg thread) -(void)go @@ -616,6 +776,7 @@ bail: return; } +//finish bottom reload -(void)finalizeBottomReload { //stop progress indicator @@ -1038,6 +1199,7 @@ bail: //invoked when user clicks 'save' icon // ->show popup that allows user to save results +//TODO: make alert have local 'hyperlink' to file! -(IBAction)saveResults:(id)sender { //save panel @@ -1079,7 +1241,7 @@ bail: //get tasks for(NSNumber* taskPid in self.taskEnumerator.tasks) { - //JSON + //append task JSON [output appendFormat:@"{%@},", [self.taskEnumerator.tasks[taskPid] toJSON]]; } @@ -1126,7 +1288,7 @@ bail: return; } -//automatically invoked when user clicks 'search' button +//automatically invoked when user clicks 'search' button/ cmd+f hotkey // ->perform global search -(IBAction)search:(id)sender { @@ -1262,8 +1424,14 @@ bail: //always reset filter text [self.filterTasksBox setStringValue:@""]; + //sync + @synchronized(self.taskTableController.filteredItems) + { + //remove all filtered tasks [self.taskTableController.filteredItems removeAllObjects]; + + }//sync //reset current task self.currentTask = nil; @@ -1450,6 +1618,20 @@ bail: //bail goto bail; } + + //prevent calling "complete" too often + if( (YES != self.completePosting) && + (YES != self.commandHandling) ) + { + //set flag + self.completePosting = YES; + + //invoke complete + [aNotification.userInfo[@"NSFieldEditor"] complete:nil]; + + //unset flag + self.completePosting = NO; + } //top pane if(YES == [aNotification.object isEqualTo:self.filterTasksBox]) @@ -1465,45 +1647,29 @@ bail: else { //'#' indicates a keyword search - // ->check for keyword match, then filter by keyword + // ->this is handled by customized auto-complete logic, so ignore if(YES == [search.string hasPrefix:@"#"]) { - //ignore #search strings that don't match a keyword - if(YES != [filterObj isKeyword:search.string]) - { - //ignore - goto bail; - } + //ignore + goto bail; } - //filter + //sync + @synchronized(self.taskTableController.filteredItems) + { + + //normal filter [self.filterObj filterTasks:search.string items:self.taskEnumerator.tasks results:self.taskTableController.filteredItems]; + + } //set flag self.taskTableController.isFiltered = YES; } - //always reload task (top) pane - // ->will trigger bottom load too - [self.taskTableController.itemView reloadData]; - - //scroll to top - [self.taskTableController scrollToTop]; - - //when nothing matches - // ->reset current task and bottom pane - if( (YES == self.taskTableController.isFiltered) && - (0 == self.taskTableController.filteredItems.count) ) - { - //remove bottom pane's items - [self.bottomViewController.tableItems removeAllObjects]; - - //reset current task - self.currentTask = nil; - - //reload bottom pane - [self.bottomViewController.itemView reloadData]; - } + //finalize filtering/search + // ->updates UI, etc + [self finalizeFiltration:PANE_TOP]; } //bottom pane else if(YES == [aNotification.object isEqualTo:self.filterItemsBox]) @@ -1519,6 +1685,14 @@ bail: //filter items else { + //'#' indicates a keyword search + // ->this is handled by customized auto-complete logic, so ignore + if(YES == [search.string hasPrefix:@"#"]) + { + //ignore + goto bail; + } + //get segment tag segmentTag = [[self.bottomPaneBtn selectedCell] tagForSegment:[self.bottomPaneBtn selectedSegment]]; @@ -1529,19 +1703,7 @@ bail: //dylibs case DYLIBS_VIEW: { - //'#' indicates a keyword search - // ->check for keyword match, then filter by keyword - if(YES == [search.string hasPrefix:@"#"]) - { - //ignore #search strings that don't match a keyword - if(YES != [filterObj isKeyword:search.string]) - { - //ignore - goto bail; - } - } - - //filter + //normal filter [self.filterObj filterFiles:search.string items:self.currentTask.dylibs results:self.bottomViewController.filteredItems]; break; @@ -1574,6 +1736,52 @@ bail: self.bottomViewController.isFiltered = YES; } + //finalize filtering/searching + // ->updates UI, etc + [self finalizeFiltration:PANE_BOTTOM]; + } + +//bail +bail: + + return; +} + +//code to complete filtering/search +// ->reload table/scroll to top etc +-(void)finalizeFiltration:(NSUInteger)pane +{ + //top pane (task) + if(PANE_TOP == pane) + { + //always reload task (top) pane + // ->will trigger bottom load too + [self.taskTableController.itemView reloadData]; + + //scroll to top + [self.taskTableController scrollToTop]; + + //when nothing matches + // ->reset current task and bottom pane + if( (YES == self.taskTableController.isFiltered) && + (0 == self.taskTableController.filteredItems.count) ) + { + //remove bottom pane's items + [self.bottomViewController.tableItems removeAllObjects]; + + //reset current task + self.currentTask = nil; + + //stop progress indicator + [self.bottomPaneSpinner stopAnimation:nil]; + + //reload bottom pane + [self.bottomViewController.itemView reloadData]; + } + } + //bottom pane (dylibs, files, etc) + else + { //always reload item (bottom) pane [self.bottomViewController.itemView reloadData]; @@ -1581,14 +1789,10 @@ bail: [self.bottomViewController scrollToTop]; } -//bail -bail: - return; - } -//action for 'refresh' button +//action for 'refresh' button / cmd+r hotkey // ->query OS to refresh/reload all tasks -(IBAction)refreshTasks:(id)sender { @@ -1601,9 +1805,15 @@ bail: //unset filter flag self.taskTableController.isFiltered = NO; + //sync + @synchronized(self.taskTableController.filteredItems) + { + //remove all filtered items [self.taskTableController.filteredItems removeAllObjects]; + } + //reset filter box self.filterTasksBox.stringValue = @""; @@ -1768,5 +1978,190 @@ bail: return; } +//delegate method, automatically called +// ->generate list of matches to return for drop-down +-(NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index +{ + //matches + NSMutableArray *matches = nil; + + //range options + NSUInteger rangeOptions = {0}; + + //segment tag + NSUInteger segmentTag = 0; + + //init array for matches + matches = [[NSMutableArray alloc] init]; + + //init range options + rangeOptions = NSAnchoredSearch | NSCaseInsensitiveSearch; + + //grab segment tag + segmentTag = [[self.bottomPaneBtn selectedCell] tagForSegment:[self.bottomPaneBtn selectedSegment]]; + + //for now, only filter binaries + // ->top pane: any (well, just tasks) + // bottom pane: only dylibs + if( (textView != self.customTasksFilter) && + (DYLIBS_VIEW != segmentTag) ) + { + //bail + goto bail; + } + + //check all filters + for(NSString* filter in self.filterObj.binaryFilters) + { + //check if found + // ->add to match when found + if([filter rangeOfString:textView.string options:rangeOptions range:NSMakeRange(0, filter.length)].location != NSNotFound) + { + //add + [matches addObject:filter]; + } + } + + //sort matches + [matches sortUsingComparator:^(NSString *a, NSString *b) + { + //sort + return [a localizedStandardCompare:b]; + }]; + +//bail +bail: + + return matches; +} + +//delegate method, automatically invoked +// ->handle invocations for text view +- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector +{ + //flag + BOOL didPerformRequestedSelectorOnTextView = NO; + + //invocation + NSInvocation *textViewInvocationForSelector = nil; + + //check if text view can handle selector + if(YES != [textView respondsToSelector:commandSelector]) + { + //bail + goto bail; + } + + //set iVar flag + self.commandHandling = YES; + + //init invocation + textViewInvocationForSelector = [NSInvocation invocationWithMethodSignature:[textView methodSignatureForSelector:commandSelector]]; + + //set target + [textViewInvocationForSelector setTarget:textView]; + + //set selector + [textViewInvocationForSelector setSelector:commandSelector]; + + //invoke selector + [textViewInvocationForSelector invoke]; + + //unset iVar + self.commandHandling = NO; + + //indicate that selector was performed + didPerformRequestedSelectorOnTextView = YES; + + +//bail +bail: + + return didPerformRequestedSelectorOnTextView; +} + + +//callback for custom search fields +// ->handle auto-complete filterings +-(void)filterAutoComplete:(NSTextView*)textView +{ + //filter string + NSString* filterString = nil; + + //extract filter + filterString = textView.textStorage.string; + + //handle top pane (tasks) + if(textView == self.customTasksFilter) + { + //sync + @synchronized(self.taskTableController.filteredItems) + { + //filter + [self.filterObj filterTasks:filterString items:self.taskEnumerator.tasks results:self.taskTableController.filteredItems]; + } + + //set flag + self.taskTableController.isFiltered = YES; + + //finalize filtering + [self finalizeFiltration:PANE_TOP]; + + } + //handle bottom pane + // ->just dylibs + else if(textView == self.customItemsFilter) + { + //filter + [self.filterObj filterFiles:filterString items:self.currentTask.dylibs results:self.bottomViewController.filteredItems]; + + //set flag + self.bottomViewController.isFiltered = YES; + + //finalize filtering + [self finalizeFiltration:PANE_BOTTOM]; + } + +//bail +bail: + + return; +} + +//automatically invoked +// ->set all NSSearchFields to be instances of our custom NSTextView +-(id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client +{ + //field editor + id fieldEditor = nil; + + //ignore non-NSSearchField classes + if(YES != [client isKindOfClass:[NSSearchField class]]) + { + //ingnore + goto bail; + } + + //set task's filter search field + if(client == self.filterTasksBox) + { + //assign for return + fieldEditor = self.customTasksFilter; + } + //set item's filter search field + else if(client == self.filterItemsBox) + { + //assign for return + fieldEditor = self.customItemsFilter; + } + + +//bail +bail: + + return fieldEditor; +} + + @end diff --git a/Consts.h b/Consts.h index 307b1e8..a30c8f2 100644 --- a/Consts.h +++ b/Consts.h @@ -161,10 +161,10 @@ #define KERNEL_YOSEMITE @"/System/Library/Kernels/kernel" //top pane - -//top #define PANE_TOP 0x0 +//bottom pane +#define PANE_BOTTOM 0x1 //for prefs //#define PREF_FIRST_RUN @"isFirstRun" @@ -175,10 +175,6 @@ //tree view #define TREE_VIEW 101 -//bottom pane - -//top -#define PANE_BOTTOM 0x1 //any view // ->not in UI @@ -314,6 +310,19 @@ //pls wait (search) message #define PLS_WAIT_MESSAGE @"completing (intial) task/dylib/file enumeration please wait" +//hotkey 's' +#define KEYCODE_S 0x1 +//hotkey 'f' +#define KEYCODE_F 0x3 + +//hotkey 'w' +#define KEYCODE_W 0xD + +//hotkey 'r' +#define KEYCODE_R 0xF + +//hotkey 'i' +#define KEYCODE_I 0x22 #endif diff --git a/CustomTextField.h b/CustomTextField.h new file mode 100644 index 0000000..cbc1e5a --- /dev/null +++ b/CustomTextField.h @@ -0,0 +1,19 @@ +// +// CustomTextField.h +// SearchField +// +// Created by Patrick Wardle on 8/27/15. +// +// + +#import + +//NSTextView subclass +// 1) fixes issue with non-alphanumeric characters in keyword matches +// 2) triggers action when user hits enter (1x) +@interface CustomTextField : NSTextView +{ + +} + +@end diff --git a/CustomTextField.m b/CustomTextField.m new file mode 100644 index 0000000..b920626 --- /dev/null +++ b/CustomTextField.m @@ -0,0 +1,54 @@ +// +// CustomTextField.m +// SearchField +// +// Created by Patrick Wardle on 8/27/15. +// +// + +#import "CustomTextField.h" +#import "AppDelegate.h" + +@implementation CustomTextField + + +//subclass override +// ->see: http://stackoverflow.com/questions/5163646/how-to-make-nssearchfield-send-action-upon-autocompletion/5360535#5360535 +-(void)insertCompletion:(NSString *)word forPartialWordRange:(NSRange)charRange movement:(NSInteger)movement isFinal:(BOOL)flag +{ + //suppress completion if user types a space + if(movement == NSRightTextMovement) + { + //bail + goto bail; + } + + //show full replacements + if(0 != charRange.location) + { + //update length + charRange.length += charRange.location; + + //reset location + charRange.location = 0; + } + + //insert completion + // ->will use updated char range! + [super insertCompletion:word forPartialWordRange:charRange movement:movement isFinal:flag]; + + //on enter + // ->call up into app delegate to process (filter) + if(movement == NSReturnTextMovement) + { + //call up//filterAutoComplete + [((AppDelegate*)[[NSApplication sharedApplication] delegate]) filterAutoComplete:self]; + } + +//bail +bail: + + return; +} + +@end \ No newline at end of file diff --git a/Filter.h b/Filter.h index 53f72dd..08eb15f 100644 --- a/Filter.h +++ b/Filter.h @@ -1,6 +1,6 @@ // // Filter.h -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/21/15. // Copyright (c) 2015 Objective-See. All rights reserved. @@ -35,9 +35,6 @@ // ->determine if binary is flagged by VT -(BOOL)isFlagged:(Binary*)item; - - - //filter tasks -(void)filterTasks:(NSString*)filterText items:(NSMutableDictionary*)items results:(NSMutableArray*)results; @@ -47,5 +44,13 @@ //filter network connections -(void)filterConnections:(NSString*)filterText items:(NSMutableArray*)items results:(NSMutableArray*)results; +/* PROPERTIES */ + +//binary filter keywords +@property(nonatomic, retain)NSMutableArray* binaryFilters; + +//file filter keywords +@property(nonatomic, retain)NSMutableArray* fileFilters; + @end diff --git a/Filter.m b/Filter.m index 0972b0c..918f8ce 100644 --- a/Filter.m +++ b/Filter.m @@ -1,6 +1,6 @@ // // Filter.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/21/15. // Copyright (c) 2015 Objective-See. All rights reserved. @@ -12,14 +12,48 @@ #import "ItemBase.h" #import "Connection.h" +//file filter keywords +//NSString * const FILE_FILTERS[] = {@"#apple", @"#nonapple", @"#signed", @"#unsigned", @"#flagged"}; + +//binary filter keywords +NSString * const BINARY_KEYWORDS[] = {@"#apple", @"#nonapple", @"#signed", @"#unsigned", @"#flagged"}; + @implementation Filter -//filter keywords -NSString * const KEYWORDS[] = {@"#apple", @"#nonapple", @"#signed", @"#unsigned", @"#flagged"}; +//@synthesize fileFilters; +@synthesize binaryFilters; + +//init +-(id)init +{ + //init super + self = [super init]; + if(nil != self) + { + //alloc file filter keywords + //fileFilters = [NSMutableArray array]; + + //alloc binary filter keywords + binaryFilters = [NSMutableArray array]; + + //init binary filters + for(NSUInteger i=0; i < sizeof(BINARY_KEYWORDS)/sizeof(BINARY_KEYWORDS[0]); i++) + { + //add + [self.binaryFilters addObject:BINARY_KEYWORDS[i]]; + } + } + + return self; +} //determine if search string is #keyword -(BOOL)isKeyword:(NSString*)searchString { + //for now just check in binary keywords + return [self.binaryFilters containsObject:searchString]; + + /* //flag BOOL isKeyword = NO; @@ -36,10 +70,11 @@ NSString * const KEYWORDS[] = {@"#apple", @"#nonapple", @"#signed", @"#unsigned" //bail break; } - } return isKeyword; + */ + } diff --git a/InfoWindowController.h b/InfoWindowController.h index b31f77d..2719679 100644 --- a/InfoWindowController.h +++ b/InfoWindowController.h @@ -1,6 +1,6 @@ // // InfoWindowController.h -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/21/15. // Copyright (c) 2015 Objective-See. All rights reserved. diff --git a/InfoWindowController.m b/InfoWindowController.m index 8a8b75b..826efe8 100644 --- a/InfoWindowController.m +++ b/InfoWindowController.m @@ -1,6 +1,6 @@ // // InfoWindowController.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/21/15. // Copyright (c) 2015 Objective-See. All rights reserved. diff --git a/Items/Binary.h b/Items/Binary.h index d150683..87a2be9 100644 --- a/Items/Binary.h +++ b/Items/Binary.h @@ -1,6 +1,6 @@ // // File.h -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/19/15. // Copyright (c) 2015 Objective-See. All rights reserved. diff --git a/Items/Binary.m b/Items/Binary.m index 9af9941..39caef9 100644 --- a/Items/Binary.m +++ b/Items/Binary.m @@ -1,6 +1,6 @@ // // File.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/19/15. // Copyright (c) 2015 Objective-See. All rights reserved. diff --git a/Items/Connection.h b/Items/Connection.h index 7420456..b5280e6 100644 --- a/Items/Connection.h +++ b/Items/Connection.h @@ -1,6 +1,6 @@ // // Extension.h -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/19/15. // Copyright (c) 2015 Objective-See. All rights reserved. diff --git a/Items/Connection.m b/Items/Connection.m index ba68dc1..1332fe7 100644 --- a/Items/Connection.m +++ b/Items/Connection.m @@ -1,6 +1,6 @@ // // Extension.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/19/15. // Copyright (c) 2015 Objective-See. All rights reserved. @@ -108,22 +108,19 @@ } return; - } -/* -//convert object to JSON string +//convert Connection object to a JSON string -(NSString*)toJSON { //json string NSString *json = nil; //init json - json = [NSString stringWithFormat:@"\"name\": \"%@\", \"path\": \"%@\", \"identifier\": \"%@\", \"details\": \"%@\", \"browser\": \"%@\"", self.name, self.path, self.identifier, self.details, self.browser]; + json = [NSString stringWithFormat:@"\"connection\": \"%@\", \"local IP\": \"%@\", \"local port\": \"%d\", \"remote IP\": \"%@\", \"remote port\": \"%d\", \"type\": \"%@\", \"family\": \"%@\", \"protocol\": \"%@\", \"state\": \"%@\"", self.endpoints, self.localIPAddr, [self.localPort unsignedShortValue], self.remoteIPAddr, [self.remotePort unsignedShortValue], self.type, self.family, self.proto, self.state]; return json; } -*/ @end diff --git a/Items/File.h b/Items/File.h index 1d4d01b..1473ada 100644 --- a/Items/File.h +++ b/Items/File.h @@ -1,6 +1,6 @@ // // File.h -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/19/15. // Copyright (c) 2015 Objective-See. All rights reserved. diff --git a/Items/File.m b/Items/File.m index 398f382..492b758 100644 --- a/Items/File.m +++ b/Items/File.m @@ -1,6 +1,6 @@ // // File.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/19/15. // Copyright (c) 2015 Objective-See. All rights reserved. @@ -93,8 +93,6 @@ bail: //bail bail: - ; - return; } @@ -117,92 +115,55 @@ bail: //json string NSString *json = nil; - //json data - // ->for intermediate conversions - //NSData *jsonData = nil; + //attributes + NSMutableString* attributesJSON = nil; - //hashes - //NSString* fileHashes = nil; + //init + attributesJSON = [NSMutableString string]; - //signing info - //NSString* fileSigs = nil; - - //init file hash to default string - // ->used when hashes are nil, or serialization fails - //fileHashes = @"\"unknown\""; - - //init file signature to default string - // ->used when signatures are nil, or serialization fails - //fileSigs = @"\"unknown\""; - - /* - //convert hashes to JSON - if(nil != self.hashes) + //when attributes are nil + // ->init default string + if(nil == self.attributes) { - //convert hash dictionary - // ->wrap since we are serializing JSON - @try - { - //convert - jsonData = [NSJSONSerialization dataWithJSONObject:self.hashes options:kNilOptions error:NULL]; - if(nil != jsonData) - { - //convert data to string - fileHashes = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - } - } - //ignore exceptions - // ->file hashes will just be 'unknown' - @catch(NSException *exception) - { - ; - } + //init + [attributesJSON appendString:@"\"unknown\""]; } - //convert signing dictionary to JSON - if(nil != self.signingInfo) - { - //convert signing dictionary - // ->wrap since we are serializing JSON - @try - { - //convert - jsonData = [NSJSONSerialization dataWithJSONObject:self.signingInfo options:kNilOptions error:NULL]; - if(nil != jsonData) - { - //convert data to string - fileSigs = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - } - } - //ignore exceptions - // ->file sigs will just be 'unknown' - @catch(NSException *exception) - { - ; - } - } - - //provide a default string if the file doesn't have a plist - if(nil == self.plist) - { - //set - filePlist = @"n/a"; - } - //use plist as is + //file has attributes + // ->add each else { - //set - filePlist = self.plist; + //start + [attributesJSON appendString:@"{"]; + + //add each attributes + for(NSString* attribute in self.attributes) + { + //skip NSFileExtendedAttributes + // ->binary format + if(YES == [attribute isEqualToString:@"NSFileExtendedAttributes"]) + { + //skip + continue; + } + + //add + [attributesJSON appendFormat:@"\"%@\":\"%@\",", attribute, self.attributes[attribute]]; + } + + //remove last ',' + if(YES == [attributesJSON hasSuffix:@","]) + { + //remove + [attributesJSON deleteCharactersInRange:NSMakeRange([attributesJSON length]-1, 1)]; + } + + //end + [attributesJSON appendString:@"}"]; } - //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\": \"%@\", \"plist\": \"%@\", \"hashes\": %@, \"signature(s)\": %@", self.name, self.path, filePlist, fileHashes, fileSigs]; - - */ + json = [NSString stringWithFormat:@"\"name\": \"%@\", \"path\": \"%@\", \"type\": \"%@\", \"attributes\": %@", self.name, self.path, self.type, attributesJSON]; return json; } diff --git a/KKRow.h b/KKRow.h index 76c902e..9c5eb56 100644 --- a/KKRow.h +++ b/KKRow.h @@ -1,6 +1,6 @@ // // CategoryRow.h -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 4/4/15. // Copyright (c) 2015 Objective-See. All rights reserved. diff --git a/KKRow.m b/KKRow.m index c12b9d5..9902368 100644 --- a/KKRow.m +++ b/KKRow.m @@ -1,6 +1,6 @@ // // CategoryRow.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 4/4/15. // Copyright (c) 2015 Objective-See. All rights reserved. diff --git a/NSApplicationKeyEvents.h b/NSApplicationKeyEvents.h index 4a4d321..cb0c147 100644 --- a/NSApplicationKeyEvents.h +++ b/NSApplicationKeyEvents.h @@ -1,6 +1,6 @@ // // NSApplicationKeyEvents.h -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 7/11/15. // Copyright (c) 2015 Objective-See, LLC. All rights reserved. diff --git a/NSApplicationKeyEvents.m b/NSApplicationKeyEvents.m index 056a91f..5a1e2a1 100644 --- a/NSApplicationKeyEvents.m +++ b/NSApplicationKeyEvents.m @@ -1,6 +1,6 @@ // // NSApplicationKeyEvents.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 7/11/15. // Copyright (c) 2015 Objective-See. All rights reserved. diff --git a/PrefsWindowController.m b/PrefsWindowController.m index baadb44..c9a79ed 100644 --- a/PrefsWindowController.m +++ b/PrefsWindowController.m @@ -1,6 +1,6 @@ // // PrefsWindowController.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/6/15. // Copyright (c) 2015 Objective-See, LLC. All rights reserved. diff --git a/RequestRootWindowController.m b/RequestRootWindowController.m index 0f8386f..54db8a5 100644 --- a/RequestRootWindowController.m +++ b/RequestRootWindowController.m @@ -1,6 +1,6 @@ // -// PrefsWindowController.m -// KnockKnock +// RequestRootWindowController.m +// TaskExplorer // // Created by Patrick Wardle on 2/6/15. // Copyright (c) 2015 Objective-See, LLC. All rights reserved. @@ -156,7 +156,7 @@ // ->4 at front is setuid //TODO: CHANGE B4 RELEASE!! //TODO: make 4755 before deploy (for testing, 777 makes XCOde be able to del it during build!) - installArgs[1] = "4755"; + installArgs[1] = "4777"; //3rd arg: XPC service installArgs[2] = [xpcService UTF8String]; diff --git a/ResultsWindowController.m b/ResultsWindowController.m index a20232c..faee940 100644 --- a/ResultsWindowController.m +++ b/ResultsWindowController.m @@ -1,6 +1,6 @@ // // PrefsWindowController.m -// KnockKnock +// TaskExplorer // // Created by Patrick Wardle on 2/6/15. // Copyright (c) 2015 Objective-See, LLC. All rights reserved. diff --git a/Task.m b/Task.m index fc7e8d8..f0c3a8f 100644 --- a/Task.m +++ b/Task.m @@ -497,7 +497,7 @@ bail: }//sync //process all new files - // ->calculate hash, etc & save into global list + // ->determine type, etc & save into global list for(File* newFile in newFiles) { //generate detailed info @@ -559,15 +559,14 @@ bail: return; } - -- (NSComparisonResult)compare:(Task*)otherTask +//compare +// ->uses binary name +-(NSComparisonResult)compare:(Task*)otherTask { return [self.binary.name compare:otherTask.binary.name options:NSCaseInsensitiveSearch]; } - //convert self to JSON string -// TODO: add dylibs, files, networking -(NSString*)toJSON { //json string @@ -589,6 +588,24 @@ bail: //VT detection ratio NSString* vtDetectionRatio = nil; + //dylibs + NSMutableString* dylibsJSON = nil; + + //files + NSMutableString* filesJSON = nil; + + //network connections + NSMutableString* connectionsJSON = nil; + + //init string for dylibs + dylibsJSON = [NSMutableString string]; + + //init string for files + filesJSON = [NSMutableString string]; + + //init string for connections + connectionsJSON = [NSMutableString string]; + //init task's command line taskCommandLine = [self.arguments componentsJoinedByString:@" "]; @@ -658,8 +675,65 @@ 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 + //TODO: make sure this is sync'd elsewhere + @synchronized(self.dylibs) + { + //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 + //TODO: make sure this is sync'd elsewhere + @synchronized(self.files) + { + //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 + //TODO: make sure this is sync'd elsewhere + @synchronized(self.connections) + { + //convert all dylibs 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\": \"%@\", \"hashes\": %@, \"signature(s)\": %@, \"VT detection\": \"%@\"", self.binary.name, self.binary.path, self.pid, fileHashes, fileSigs, vtDetectionRatio]; + json = [NSString stringWithFormat:@"\"name\": \"%@\", \"path\": \"%@\", \"pid\": \"%@\", \"hashes\": %@, \"signature(s)\": %@, \"VT detection\": \"%@\", \"dylibs\": [%@], \"files\": [%@], \"connections\": [%@]", self.binary.name, self.binary.path, self.pid, fileHashes, fileSigs, vtDetectionRatio, dylibsJSON, filesJSON, connectionsJSON]; return json; } diff --git a/TaskEnumerator.m b/TaskEnumerator.m index 548da4d..26c9a6e 100644 --- a/TaskEnumerator.m +++ b/TaskEnumerator.m @@ -46,7 +46,6 @@ //init binary processing queue binaryQueue = [[Queue alloc] init]; - } return self; @@ -161,6 +160,10 @@ continue; } + //nap + // ->helps with UI + [NSThread sleepForTimeInterval:0.01f]; + //generate signing info [newTask.binary generatedSigningInfo]; @@ -182,6 +185,10 @@ //enumerate [newTask enumerateDylibs:xpcConnection allDylibs:self.dylibs]; + + //nap + // ->helps with UI + [NSThread sleepForTimeInterval:0.01f]; } //begin file enumeration @@ -193,6 +200,10 @@ //enumerate [newTask enumerateFiles:xpcConnection]; + + //nap + // ->helps with UI + [NSThread sleepForTimeInterval:0.01f]; } //TODO: add network connection filtering @@ -457,7 +468,7 @@ bail: return; } -//TODO: test w/ dylib!! + //ensure that the list of flagged items is correctly updated // when a dead task or any of its dylibs were flagged... -(void)updateFlaggedItems:(Task*)deadTask diff --git a/TaskExplorer-Prefix.pch b/TaskExplorer-Prefix.pch index 06533d7..8111220 100755 --- a/TaskExplorer-Prefix.pch +++ b/TaskExplorer-Prefix.pch @@ -1,5 +1,5 @@ // -// Prefix header for all source files of the 'KnockKnock' target in the 'KnockKnock' project +// Prefix header for all source files of the 'TaskExplorer' target in the 'TaskExplorer' project // #ifdef __OBJC__ diff --git a/TaskExplorer.xcodeproj/project.pbxproj b/TaskExplorer.xcodeproj/project.pbxproj index 477af52..f93a9a7 100755 --- a/TaskExplorer.xcodeproj/project.pbxproj +++ b/TaskExplorer.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 1D21BC4F172AF43D009D1CFD /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D21BC4E172AF43D009D1CFD /* Cocoa.framework */; }; 7D2F567C1B81BEAB00C7D85E /* SearchWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D2F567B1B81BEAB00C7D85E /* SearchWindow.xib */; }; 7D2F567F1B81BEB400C7D85E /* SearchWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D2F567E1B81BEB400C7D85E /* SearchWindowController.m */; }; + 7DAA78091B903BF1004840B9 /* CustomTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DAA78081B903BF1004840B9 /* CustomTextField.m */; }; CD001B381AB903040089014A /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = CD001B351AB903040089014A /* logo.png */; }; CD001B391AB903040089014A /* logoApple.png in Resources */ = {isa = PBXBuildFile; fileRef = CD001B361AB903040089014A /* logoApple.png */; }; CD02194F1AD34D8B005148A2 /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD02194D1AD34D8B005148A2 /* AboutWindow.xib */; }; @@ -155,6 +156,8 @@ 7D2F567B1B81BEAB00C7D85E /* SearchWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = SearchWindow.xib; path = UI/SearchWindow.xib; sourceTree = ""; }; 7D2F567D1B81BEB300C7D85E /* SearchWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SearchWindowController.h; sourceTree = ""; }; 7D2F567E1B81BEB400C7D85E /* SearchWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SearchWindowController.m; sourceTree = ""; }; + 7DAA78071B903BF1004840B9 /* CustomTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomTextField.h; sourceTree = ""; }; + 7DAA78081B903BF1004840B9 /* CustomTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomTextField.m; sourceTree = ""; }; CD001B351AB903040089014A /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo.png; path = images/logo.png; sourceTree = SOURCE_ROOT; }; CD001B361AB903040089014A /* logoApple.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logoApple.png; path = images/logoApple.png; sourceTree = SOURCE_ROOT; }; CD02194D1AD34D8B005148A2 /* AboutWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = AboutWindow.xib; path = UI/AboutWindow.xib; sourceTree = ""; }; @@ -328,6 +331,8 @@ 1D21BC42172AF43D009D1CFD = { isa = PBXGroup; children = ( + 7DAA78071B903BF1004840B9 /* CustomTextField.h */, + 7DAA78081B903BF1004840B9 /* CustomTextField.m */, 7D2F567D1B81BEB300C7D85E /* SearchWindowController.h */, 7D2F567E1B81BEB400C7D85E /* SearchWindowController.m */, CD74A0781B7F170B00A8AAD3 /* FlaggedItems.h */, @@ -774,6 +779,7 @@ CD4D541F1B2CE6C400008030 /* Queue.m in Sources */, CD6E54FF1B1162B5007953AB /* ItemView.m in Sources */, CDA81DEE1A99B5F8009790E2 /* Filter.m in Sources */, + 7DAA78091B903BF1004840B9 /* CustomTextField.m in Sources */, CDA81D7B1A95D29B009790E2 /* TaskTableController.m in Sources */, CDF08CCA1AC4C678009B3423 /* PrefsWindowController.m in Sources */, CDA81DEA1A997BF1009790E2 /* InfoWindowController.m in Sources */, diff --git a/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrickw.xcuserdatad/UserInterfaceState.xcuserstate b/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrickw.xcuserdatad/UserInterfaceState.xcuserstate index 10eec8c..33e98a9 100644 Binary files a/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrickw.xcuserdatad/UserInterfaceState.xcuserstate and b/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrickw.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index ce427b5..ab3133b 100644 --- a/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,22 +3,6 @@ type = "1" version = "2.0"> - - - - - - - - @@ -211,5 +179,101 @@ landmarkType = "7"> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcschemes/TaskExplorer.xcscheme b/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcschemes/TaskExplorer.xcscheme index 8d3ff25..ee52d29 100644 --- a/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcschemes/TaskExplorer.xcscheme +++ b/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcschemes/TaskExplorer.xcscheme @@ -59,26 +59,6 @@ - - - - - - - - #import #import -#import + + +//interface for 'extension' to NSXPCConnection +// ->allows us to access the 'private' auditToken iVar +@interface ExtendedNSXPCConnection : NSXPCConnection +{ + //private iVar + audit_token_t auditToken; +} +//private iVar +@property audit_token_t auditToken; + +@end + +//implementation for 'extension' to NSXPCConnection +// ->allows us to access the 'private' auditToken iVar +@implementation ExtendedNSXPCConnection + +//private iVar +@synthesize auditToken; + +@end + +//function def +OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement); //TODO: CHANGE B4 RELEASE!! //-> for testing: @"Mac Developer: patrick wardle (5SKKU32KLJ)" -#define SIGNING_AUTH @"Developer ID Application: Objective-See, LLC (VBG97UB4TA)" +#define SIGNING_AUTH @"Mac Developer: patrick wardle (5SKKU32KLJ)"//@"Developer ID Application: Objective-See, LLC (VBG97UB4TA)" //skeleton interface @interface ServiceDelegate : NSObject @@ -24,7 +49,6 @@ @implementation ServiceDelegate - //automatically invoked //->allows NSXPCListener to configure/accept/resume a new incoming NSXPCConnection // note: we only allow binaries signed by Objective-See to talk to this! @@ -33,63 +57,32 @@ //flag BOOL shouldAccept = NO; - //status - int status = -1; - - //buffer for process path - char pathBuffer[PROC_PIDPATHINFO_MAXSIZE] = {0}; - - //code - SecStaticCodeRef staticCode = NULL; - - //signing reqs - SecRequirementRef requirementRef = NULL; - + //task ref + SecTaskRef taskRef = 0; + //signing req string NSString *requirementString = nil; //init signing req string - // ->check for Ojective-See's dev cert requirementString = [NSString stringWithFormat:@"anchor trusted and certificate leaf [subject.CN] = \"%@\"", SIGNING_AUTH]; - - //get path - status = proc_pidpath(newConnection.processIdentifier, pathBuffer, sizeof(pathBuffer)); - - //sanity check - // ->this generally just fails if process has exited.... - if( (status < 0) || - (0 == strlen(pathBuffer)) ) + + //step 1: create task ref + // ->uses NSXPCConnection's (private) 'auditToken' iVar + taskRef = SecTaskCreateWithAuditToken(NULL, ((ExtendedNSXPCConnection*)newConnection).auditToken); + if(0 == taskRef) { //bail goto bail; } - //create static code - if(0 != SecStaticCodeCreateWithPath((__bridge CFURLRef)([NSURL fileURLWithPath:[NSString stringWithUTF8String:pathBuffer]]), kSecCSDefaultFlags, &staticCode)) + //step 2: validate + // ->check that client is signed with Objective-See's dev cert + if(0 != SecTaskValidateForRequirement(taskRef, (__bridge CFStringRef)(requirementString))) { //bail goto bail; } - //create req string w/ 'anchor apple' - // (3rd party: 'anchor apple generic') - if(0 != SecRequirementCreateWithString((__bridge CFStringRef)requirementString, kSecCSDefaultFlags, &requirementRef)) - { - //bail - goto bail; - } - - //check if file is signed by apple - // ->i.e. it conforms to req string - if(0 != SecStaticCodeCheckValidity(staticCode, kSecCSDefaultFlags, requirementRef)) - { - //err msg - syslog(LOG_ERR, "OBJECTIVE-SEE ERROR: SecStaticCodeCheckValidity() failed on %s", pathBuffer); - - //bail - goto bail; - } - //set the interface that the exported object implements newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(remoteTaskProto)]; @@ -112,14 +105,19 @@ bail: int main(int argc, const char *argv[]) { - // Create the delegate for the service. + //create the delegate for the service. ServiceDelegate *delegate = [ServiceDelegate new]; - // Set up the one NSXPCListener for this service. It will handle all incoming connections. + //set up the one NSXPCListener for this service + // ->handles incoming connections NSXPCListener *listener = [NSXPCListener serviceListener]; + + //set delegate listener.delegate = delegate; - // Resuming the serviceListener starts this service. This method does not return. + //resuming the listener starts this service + // ->method does not return [listener resume]; + return 0; }