From c813cc583ddfc260dcc3284f5a3ee368cf845881 Mon Sep 17 00:00:00 2001 From: Patrick Wardle Date: Mon, 31 Aug 2015 22:00:25 -1000 Subject: [PATCH] v.1.2.0 beta -autocomplete for filter boxes -keyboard shortcuts -save includes dylibs/files/connections -sync on filtered tasks (was crashing) -code cleanup --- AboutWindowController.m | 2 +- AppDelegate.h | 25 +- AppDelegate.m | 549 +++++++++++++++--- Consts.h | 21 +- CustomTextField.h | 19 + CustomTextField.m | 54 ++ Filter.h | 13 +- Filter.m | 43 +- InfoWindowController.h | 2 +- InfoWindowController.m | 2 +- Items/Binary.h | 2 +- Items/Binary.m | 2 +- Items/Connection.h | 2 +- Items/Connection.m | 9 +- Items/File.h | 2 +- Items/File.m | 119 ++-- KKRow.h | 2 +- KKRow.m | 2 +- NSApplicationKeyEvents.h | 2 +- NSApplicationKeyEvents.m | 2 +- PrefsWindowController.m | 2 +- RequestRootWindowController.m | 6 +- ResultsWindowController.m | 2 +- Task.m | 86 ++- TaskEnumerator.m | 15 +- TaskExplorer-Prefix.pch | 2 +- TaskExplorer.xcodeproj/project.pbxproj | 6 + .../UserInterfaceState.xcuserstate | Bin 42522 -> 64019 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 134 +++-- .../xcschemes/TaskExplorer.xcscheme | 20 - TaskTableController.h | 2 +- TaskTableController.m | 2 +- VTButton.h | 2 +- VTButton.m | 2 +- VTInfoWindowController.h | 2 +- VTInfoWindowController.m | 2 +- VirusTotal.h | 2 +- VirusTotal.m | 2 +- kkRowCell.h | 2 +- kkRowCell.m | 2 +- remoteTaskService/main.m | 94 ++- 41 files changed, 945 insertions(+), 316 deletions(-) create mode 100644 CustomTextField.h create mode 100644 CustomTextField.m 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 10eec8c3ed83731be7543f48baeca46d48cf4a6e..33e98a96d6b69ec24a3c90665a4da871c9808a67 100644 GIT binary patch literal 64019 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%0DO!NcLWj;;Xg5tJbkM^)~gP z)SN*0ZtF~a34i?Gp(dMBlSBCKnW%Z)6iiTKA$*S%HDT#5wJxpc{wCN1pSEkrG{`jA zcp=|Z3;{1wfvM0`WEyH3W*Sa-pcKeFiSXHk&n3L~2Ga=BNYg0OXww+eSi<`WKa%iM z2wy??g@j*hP;#aTY`XL*RXec#WMXmdQ1Bb6y<_V>y6GlOJ(#94^b#XgZfp;`0dKA2t#W^_-bKVoG+w++czY7nvZ5xxuv9w8V5V z;Rh1_WWo>HV7krCqjUqJXm!WR*K=tk2<(RAbAqhc z!E}3y(+T$8ZMw(U#cigCLF_W_S~WdldK5BUC!3xiJP6wuNOzSHetfGhX?lvHKTY`2 zFph!pK=$Ok>_Ee)L_^P;UV?EnYzQ^IYnOsXV&X)BXt3=PQw;iqM4NS0;mp-hr( zvR!r%eg@%Z68_XAlVq0+<~xlj`3M>6farxDL*|c54+G0b$qL`ag^C z;96B}k|T1I8zhHhn8>FSs5k&Qsv>;j|23s07gA=x^qvFbIA(5Hc5r-lMLZ$EaKPThdyi{J+W^OK*uaH*|o)CT!;m=LY&8y_AjYM55U)RnUtd%!qoSQeuo8=qj zn+OlIUO@N@38bvj6<9Fj2jw2|GX*AZNztC*%$@RGMk4N&AEJ}-X1h#99oK`j_FMHO4 z?C6A8f-f)1yXfS6Nq$*=h49M>e+A)JY>;=$d*r=@UrG2Y34hhUX>z_PzhxYgcjfom z5sZ)I&*h>E0`6j@Oevtm)KicPUA z4nqMd_*>sT`#otsJ8qs~o3vQ;t`8qTm zoTT(qvXvYqSMexb#i#g{fD%+fN?3^~QKi2!KpCif1L1768VB;ol*gg-#|gM|N)@IMp&SHk~J z_&*8e2<8bE2$l$z2{sdKCD=}|O0bLIEP{_9xC_BY5_~kl#}eF);O+$XB)B)heF#30 z;C=+>5bPn?M{t1P5Wx|G`x88nz+NLfgy4LF3ke=d@Nj}h5IlixN-7vU;;3k6S5#iH9l>h|-aznXf^Q)hcytHBcN2Uc!4DApFu{)z{0zZ63Eo5SYXrYZ@Vf+mNbn~F ze@^gM1ph$r&jkNY1da%p2qq%fiQptcCn9tq!cjyxjtD)7Z~_rdBtkY3yhI2PAxwk; zL>NSbJR%ekp_mAx2^1#_6A2U{3)6{kDiO+v5GTTHBFrH|EfE@s&`gB+L}($x*+f`G zge63{j0nq#u#yPZ5aBu^tR=z*BHTcPn}~2L5w;THE+X7Vga?SQod}N-;Rzx5u-#LNW{TJ%qLQyxDU<&(_4R1&)E-;HO@Xc7!5`I{;0Dv2G+1;~sd`TWj+Rfu1|9u8!q(Go zV)ga8v_J}K-5PTX=?N{ck-Q;y97KwsnyjV~j&La-TQUWbQ|v<8GbM#16Suvd9Qd!X zVoomGM3XzJsth(DF38mfORub=$AQrr0)3q6mE6{om>P2na)(2PhaM{iyPlx7oL14~ z%uer%jrHZ0QgzXPurJMx4Y^gd<<+nsJ-4i}CO6g$2O1d(b1}khJN&{8S&1+dX8EI# za*4qCM&69V9fKzz!EcbG&Wxt5ho7cEAQFuPJ&|zK=ZBP9z~>KSnsBl5SVg5#wvT2s z-O+KHqU9CAia=TVWYU#@FA5ujLjjL36!m(8-Y87E3^bKR0{${%355B0M$=uT>d6VA z`um6570oY?*VBn)l!x97^PW<5$Unz4gnkY3e-eYe52`i(IqamW2B_PwgY9odT0IOD zHW7AA304F>l@V`6wC$L9gF&w!vVqpFjK~?+-OvfWz4O!X8f`)9i^xV`UY7gRf;S zV>G=`s-AV2eXU@~&;WA&mjqVDCBVj+xI5=}X0;j@~lQ zLm_|2lTim$hGWry(Ivf{(e&=WUMUCr5G$+Z=hT-Q{G$~N|NXC{69VS7A`Tl$Kz?@AU`59-qIh;;RUHV`bh7pHUaDW;E?T z9G;9p=r%CmPfFE?64p0O@EiZ*i_M|b>(Upx|J#BS3ckysTv{)VG{}^G#wg)GD^)M} z4|XYi&DppB4%!CtN~>sjsDZfK7{9;hsBpoWu^mO}^})YUzaI{9_rqc@6pZ*Yni@ZZ zT*3x{zmw7Q)xVP_I8i?62}Q!0)_Ac>kH0)-wDk8en!YJj@BR-CV*5dmwwuOMVUWCJ z7^HPWym81MVqJl1;|{Ds^lIZ;_;ySK{j7))_%Q|yWyT{DJo@|VV7C5fWW_&ezPUkK zpJFZi@UJ-`2FT|bpR z_-8=E3{m$x?287$70fUzV=!YQej`nL8BM=+NNm_@gZ`x3tBhi^j{*Nts`mfqVgr%_ z`>TzB?=nEBg#6b9++dtO(t$B5RUb%D`d>9pe_ILA?Q>P-^z#BnPJPBYggaCR*m~Sm z>qalbr23lSLPhx41f%|mCV;OtH8zchlXu%_1H*89&ss5cD44}0awwE{8d>r)1BK$? z4it=1?b!C@zqDBT9}FGp$vYsLhfJb$URZbr4E5z5i^0V`QI9WB;q#@dC_OF;VdHQZ z){7qas1+<8;nQ8-OolvGSpnf=gO_MAF`}SW{cvY&AZ|=4359%6-tGzdebGn;H3q@j zikR16$Sqbzle6PAMcauBhJt=Le!=U9KRi)i2qKpm`2;+Q#0;v{;$$?rJ5E!3PFQNC z1&s1W{6SwvbzfOoQRa!HJ7Jb17)_lHgK97c>QM~0^WpDn%Hf360tiUhmx=$;Sb3#C z=rQ!Q8>6Y~zmukL09u7$aoZIZco4pL*zYrJ7_szXG#&MCr3rihh+rEMqKOCwWIMwZ zapQbGkun=-B-EqFCH*-Z8C$)g_8vmg8KJ8!Bd*y1W=0|P3;FVVFE++G6EhL^#y}a;?R~)dYaf9jTzF!mU>20v{ZdC zA)o(M>+`plbmQY^G*`jiY)AtbRd6%w(SU#XQ+o!3b0$MQx#R1LNTtsohh4F00?gJI zu(8?)*_5D{?iL22fwe~3Gf98M<0}gXj6Ui)jHto?2BJK^CB1z%0e-3xiJW4xq{I&tW=$xn3{jzbhJISpv@H{i?3#_6(4>MBpeM0x%nax?S{l_ z2Kg?Fz`u})PFI-MGnz)0s>A;|2U5FM)1s+{L0`|{M|UU~0cWwdSB_%Y%z(!peqN>u z$2@*;ZG3PVO$J`Z!XAGme1bcjm$x#SPWd;`6ooLVKj8EEy%DI}r?K7)G({`Jaeu@( zzIQO1N;)*H+sJcnn-s3)Ue>_G4mHqDv5djJgO52uo*hr_xCU-7a^dy)f-u4m<_UrR zg?uSgi)3PJg*P4w8vU_H8BtRY|4^qw3{XV@2V?ldnT8tH44y#1IMh2BP16rQO`uSq zrW4o|_C@HS8o@|rK7}FGYbd3%JjZC7S*jj}FSHrN;w6S$dN?$TL1pb>z_EX;yam7) zfqZ2kqiUgr9gsRqS1oTaqRJ2d*d|N|tWlvbDMA%Yra4dvcPfm&_B)KGc!xqYX|Ztw z_djHyvkrepQxhu~4FtSFP?Q-u3URVPC6p|rGyhXYQ&oo|EotWsq!~$rD1XK9=XA)O zOBMGT_;`R})^wJ~`U;(WyJ^4fx*-9AC17|nj1Qy` zprX$>KEE@f&gc;T=FEYw(-g$3jRnV6#4sB>WY*w=amn*M(gr=9lU9i}(A=Q|v-}Me z@ClWs1-WAzU@t*wsvAPwH&sbnxV%>GW3Q2wsnG`wc}W8=Nx|iLPc1@j|@N-36=_()(NnR@P=Xr zLvHQKXdI2!jsJ}uI(iACA$DQy%Q|vyhm=KX?Q9sZ9EN#*$F*X6J4U?l zsT1(~{T?r5`=VY5U4$}aoFehc@@U+!8nOBrQ5XIjq{svNY9LV?41}SA19nYh2!-hx zr)Z$k;G$Y1jHbnhpQcn8GYI9?u*oj7<^ahqFE>~Z>&c9!i#tAe?XEAtkU`O7z!&v_ zwhB_WBa`0@E{6|ty6G;5HJ=f6>EUOT*YAZzPb3uac;Tz#5L(SxodZkBsHegZwzm#r zG+o}I%BbY71%nYB#UPg*M$gKCe+t9D;_$OErAZ;T83kX`lSz|CDubao&3>@MaI6!o z^s7GBiPlNh$yV4oy^`Q73BHQpt2bJwS*LTotTPFGkqWPZv-R<{^xT3jSTdn7M7Z^UcGjn)VMLe&%I`PrZUWdGt_~L@`EjVX%X`bh<+b+|; z{0$vkIEj7&+>`k3gLRg*y4{Y{SZi5Ft|xePYe#gz*{4+eGNT1f4TRG!e9@>ciPmg| zBL&)i3EkRag%3_zVWaq3g4a=`^>A!P-h(TbHSBwF=!_P+g$@q2i3EcSr@=4&sQzv8 z1h3Dt!Ws4L@Op{$VgSCB;Ee=tqQE!kz{{`foAXlBj22KJfD7Rk-$G~wrU{(A1QRq# z?-f=!5~LmIYpkmP8uoABNbpS*8jj5enpZrL|K;Qv(^~=%h=%Wj2BQn7wm@A!a3Yw5 zU2omoF6@m~_|6xVj#~+)UtXZnp)<96kKWO#bZ~sZU?2%~yA_UtYKOvmt@kmgtpwkm z4%KJ%-Yxoo!;Z-$(!uv^h z55f1QK|QqP;%6Syi3-5sMTxQ9XMGotxO=#Jx%;?nT2$+=4bTQ|w7zG3pP_6c`2G~i zl277C{(QQQ0;!}V%BR+^GNOEK{RU9JBltmrAEGF5#6IwP$-ax83O=QG3*sEQ%z32f3m`185!vM!}=$q>k)zqt14*f|b+#Ryi4nGAZ190PTQ`EACHOf? z{_~8rFAqLtTad|~H?(j{i_ep;Zfw16CsB@^VC!S+YdewP7YKfl;Fkz~d84hLEgK+n z34Vp(T@-Tne^qZ@sJuRO_D6bGf}lU9wDjv2; zP(t9Kz#&a3;hSWG6Qn4Z+_M{M|;|CL4V5%?5Vn0Kwl=q=Wxe$v=Nf=*O!U z>pg&)bUKLx!0XhI-D$gzb|s@)+h)6;G37^s;hWzH_AdEpSAN+nre4yFlI>C3j&`A* zvONu40sZm|!M{?d-*gFBa$x0NiqLw z#oUnlV!qzvFnkpYAcHWMQayghMh`U1Jl8+6?bmw@Us@ITRF9YDb-&^6H&~Cu{**+0 zY5T5S>JHf8lVLVE6-6L|2vCC5O5Jtx=hJoS;CK(6I>Xfb&CXHC-)(=`{gGH}#{fs~>6YN^)}8ZFX0C{I$F7@S!nqm;?tA)O7y# zedzl4bfh$0YCpuM<`Mz3t)fL{up!?&Vh;$Axb;jpNZjtWxam&x2kR3!7!?A`?K~J+wJK~_LmuT z1Bq~Qin^tFJtmDdGaVcBCf5V@*X(ehUpq{D+x`xN0?LM@Llw=r#o1=c?zey5F47nF zFQKDwl2twt3TQ_Q*{BW@Hq0)b#ysDkpU$|j4>U^BGqWGG|I#k#ulC;n^baBoCBiTY zI$Q@`zV`b;*FG}61-@xO7s>(Z^@PCVOPk0J?0|1RW$v{@ahSPY4zK|uh%l1Ej$*Vv z`q(EYO0YDBZ!ExkgOU^KNJ5DqOt*yCI9!g-?c#QEbOl^Eglr5E#!}pIjNJJqeAVO` z;6p|M${U0ueW$fRppmj6sd*g7J9-0fMk#Wf;OGOuClX;i5lSfV1ReOY{aoEyll5VO zoIkJ@B4Ft}@Hm11*~Xi6gdAZu>XV2t8Q@yIzLghoCoJy^-XugZX#YWzwfd!wL5_lU zdtK-#VrWx|FfE0)B7E!plW(00PB{bvLdi9hW0d2RcA>^QN*L4(BFs!dEzR3_;rsW4 zCJaRaz%BNBisQbaEKioCLZ2vZI|f0A?a9A~vl-$KXP45^X`vr#Z9FeHX;EU=+q=_S2)@Y z_&Ba{(0~t^;c6n(q|lb;^}O`0bD3sHqidbxhIZ-N?6?s|;btP#5uqNCgfp1%e)PLs zxm!nydg&Oz77rMM6m_>d+70$N?sc$WkI+bjrgZ8?FYebz$4s+Ej)xuX274S&I#{qr zm`j9t=}3j=J=)R+X{V#zSdU|ugT;D;1w=SA9ccvr-CJ#t_Bm**C!;BIyysx?9^ot^ zEKEl#zkOS09VwCu+c`dU(0ET~q^}(i^KpDfgmZ|X0g^y;-miFKMesA2=|RXi0?%pC zlCGYqwe_Y%_a_Gp`#64f{Nni4@f#7&CBk_`IG+d?Y;^qLfY6T$hcI49go`LJd_Cg7 z=t+~ucWNuQ!s1%j+z>RSo)XkEgTRN95~Z3|_^fZ{c#Ep4PUw)Ez!%7cOK68K)j4$` zH>%I21K?QMKnSE0>V$M;*wF{j!KCt5yQ;^wHwNl)YBxacPK3*eK)(zuEMr<|{Rc&t z+)l}ifYOG%1T{8Ls+Fd|)jn!=yRbQGE}NiN5Mc$tCYSCf9r<)z7m~E4yBbpA10tD) zL>;J}3t?T>_#s$X57mtx;G^!5(Lh5|M${s8M7y{n)lrPX zRYbTpokDNa2EAXRB3=zI8-cpHAO^wXc-3X%H_tl1K{q0Hi9fPn5^U+bsptxH{Rhq-U;yz>d-t-gq`U$j$V30>l{t7 z&e_)qpN+`8I&|hbJwPKYGhZaaOO(c!bsEo`NiM8@k$U#J5L0awq5_@DIyocGj4==A zAZKgLV;2#2r)XR9vU{BOT_zzZ$H+PK|F!lVa7|v{-$8j?fRMr75cb}C?+pnMAS8(F zAs|yklt)1X2Z*AK03nPdgaCm6VFbb+6>!wn)~&Vft<`F^?-Quj{;I$K=Y9Wg+fQR2 zZ@$mH=iYnnx#ymHzHq+Fwd;-yuIaQd9FFi{_%{!3;Sun) zgImE%px~;n{gY1f!|(aHHt(SaDd=u#;ym}4kS5WZomq8q$4G@WGczabu!It9@W( z{I`1q-U081p#I$lJ#ZFGP2hJ>@DV8Z7~+m@S?hx1S3T!^*JkJ6D>|G5=RzR=S_j}` z@Nr1&khXXV3O?hD{rQ^M&q}d{kbj|ALcIuGRr#_P=iq0)*}NtA@>=s=f`YHUY@W^W zdZB-b{30A;*!jk=zYM>!hV&aK_~uI_4a)f(ghfY=CALu>{C7zhYJ6$I9;^}~zugwIyh+Ik0R#?Q+NLKK1e zCZsq*0)mtR0YMM|_>g~j+Ft=jsQru->oW(Li#&y7YXAXLBAXapLe z2H|Rez$Osb%*WlbTGf+wA4^n!g+eBvurEID>Zz4E6c6@=Do5xcjK5i56NKriFaco@ z_~gcibV~W-=U0AP`D@(#-Etd*BZTy?S%GjuIIp3Ig1{H9^y<=1#E~@z_!s&O;f3)3 zDoOw%5Naa^1P~y=ccTMqZuII>dT(R~-&zWlu)Ai@xYZ%U7d zh=P!#K|lfoB>BivtJ13?v+|#oKzeS~I{+op<#&n&-$wttQxXv0jAKFUKtORUhzt;r z0RdS)EJUivxAv*0PlxB@3(TOj29Qt9!W3Fxp+4bv{AZsjA{SBc&Fbz!6t0O=0R$8w z+|T=UOYx)CK?4P9^Yxb*1pl`v zDTr@IuOMg$`dX8n=9G!R1|pbG+e{MzZSwb=)0 zt-?P8fMO+~YMB^9CMSQ(2t~N@zkOPL5#xwy2>Rb+dkGQzHm;k0AVD`mga?5M^+?p6IJ{ygQ zD~OvA$X87j#4W__HR)M^faTxPJE`aQ0?&b>m7vg2sB%^opN(w91H{vBqCG=AUqiD7 z0h_n-BLH<3Of{#-+{0|EOlk?gM7*{}8L7e|=L4M?GHK_P{a zB5P2NAmH>Rl&Yf4noVJ9^;uCNQAp`;BFP|S*N|L5!1YU{A}*(6t$9YDn}<|EYCuR| zJ=;KPBDG*@NF5Mx2LZlM9a!_J7e|!BQx^Gd!d2(5$*N0^A5r;*Qbrmf&A(Yg3#284 z3rXJ_1o%#M;4i1Tm~zmt-Cy&Izb>tMT0f6mq$ASpn~?5EkF`eog8<*3{#m62Y#$u? z*Ze~IBO%W+1sQ-01c4wBz*3MHBo+ihKp^xVd>F{J^v?h$O3dO@)(D7kAzm*gBL^bG z$c)QK&&>Wh4~AvDMXU*QN%D((G|07#&p`11Jz6xB<_@}t`6-SPxeXcp4^kpzEHdt& z4-%13+(|Ps2?WBLktrY$@y~{>-N^@_`+NUOI&}SAF;L1gDD!2q!Cx91nT6yZGa|E* zImldO9thw;AQA)!AV6$I<|B6_3y^z2APNMax+R0aDqWst7$3z1%I}#BrON&*-C0(8 zPG(G;Z&Fsaeip=qk(~=sO+<<5ex)ds%_TP}9ZH-ZotY5#*Uw#2A=;9!7ylIV`6;IK z6DKiLT>UHPQESh>1pn0314w9_(2Ohwfo;vmgCGDM8$jI3uA#wLeueqzm{+e7t@1J= z%V9MX)v;~Zi9e?Gmk9@+TMSFU$Ml0kuW zAdpz(r0RGawOTq#4Ds)#ueGHOVkJYig8)Ps_&t&idkuR96GC<%8OTl~GdwFUbuHmJ z2&93)4iHEM0jTQ#|3(^Qg;d&nm{Py_sXZ(tn?Ip@k$oVL4gwhzWWS&*2v3WSv3I@rThY z2;?*)p*1+~pObZIHHsU^g_7a?LksPQ+mW8hXXW{Cis(O8$5|wFF!2S|Ch~ja4=_YC z@*)W2HzR)pf!)xcgE+hWqj$t%von*@5+YX-ev?LpR%EGUKx2@H?Vo(zAB0^6Es1sl?`H$F9~-FEu?TI#n+weMd$P zlyu2@^=o2!R(9wvOY?Zveo z#KtA?lbAsIP3m7i30P%Df&Pk5#QsmT;H2!-xYhZN5`opUp|+w#QDP`K3fjO_fWRpb zr~`pU5TJZkWhikJWSF#|Bv6tlXp>k80+33pYC*}MWKnV;Z~_D%y;jYyy86Ff_K%uE zLHg;LP^+TEbpCrmXNZmp8aSaL!KPv+I{Ohe0lz5R~5kQfYD%w!AxkxC~AL|bBhCn4< z(jk@ey}W|r7A0jBRp>c3X?Gm-)_6W&M;7#*ucK5Q)z=yg7ZEGkShOihOiAA}E;SdL z1u@b7P;x8RxU7`V6b5?E>8Y{j)ipG=BGxLM20?_a{V|RY$$vFPQyY3qz^YI#=<03J zH!y^1gX60WsEw<&!e&Ir#5o(n#MT#Wf#8fyK2?`utPOoPGlvTPeJPb<#(!vO1?ihF zWE8t5J&HBNZU2?$QDUP0ScrxoE_PLALA`R$&OSI3O4(W@0NVtE!(?EpFg=(t%mU^H z^MPStc-VGWI&3E_3zh@h2RjHm2CIdUV2v;`tOeEvqr&L09vBBU0h@=NgI$8%g#7~h z6?RWRNI*KPng4BpMQM_EP(?0pJsqRDg>t94k?5{R1iuEg+&Fgs)ZI1XoWNb2(*Df zdpjz0ohT{{6~0b%9pqu57STWe`mdvi2K@`^3YGspsrf1L{>MVD(b-TTjsK3sFNDp` z=Pxw>LrMNEq}7t2)yR2N3^dnbQE~hco(=sqWTRA9JiIS`9R%p-A(=0r5>ScI0OP-e zf=Ym$AqJ<%M&~O6M?4BC;JeUfy#o68`MrzCJ1zaKo13# zjmiOmUJw}khn;|0OCSYw|F2Cgz10l+s|w?b{R?U>7Zkwyf4UR?jZ`N)K8=+9s1nE| zMIAsv#x!Kbu_>s7D99k_2Z4bir;xm)*zCm6#JHq{#B5c^vbCY6>Ufa1`lnAA$56-r z1EUHBEjkqoT4q|H!Ks7;Js2Q6R`NW)&d@@iz+bP5~ z3GK|G`!Eod=YELI6Y3p-&lH8IwPZZN=>Pv*%wJIgC>dP7lPF`|1! zOGT?iJ4FXYmqee535rRIp~bYsbj0+;jKxf$2n9LAz>vEC_#{jm57%}lt`A?DUl_SBatVOFR@>uM50upOyZP8n*>#Y zF2RssN_0!GB>E&KC8i~2CFUg-CC*4JOPrNBFY!)tqa;ESB`F~(B`G5*C#fN+C8;B+ zCutyQBxxcUAh}brTXIVBisT*1dy)?%A4$HE5|k2>5|I*>f=fwB$wcPO7D{1Exkv2uXK@guk?uYr1Z4(tn|F} zqV$i_x1{e$KahST{Y3h`^aq&@GHNo~GF~#FGGQ_iGLbS#GAS~ zQg&K)R`!DI&$3r!ugTt!y(@cP_Mz-!*{5={awc*Yfxkfp%97V2GjwQ#HyCnBWev`bqys^B$JYGIlK3+aiK3P6h zK1V)JK3~2-zEFOje4YG+{EGY=1(?ES1-OE`f~JDDg06zTf}w(yg1v&Hg0q6FLXZMh zAw&VEkfN|dVYk8_g}n+z3P%;{6i5n<3S@;}6-5>06crSe6jc;;6!jGi6^#|W74eE0 zisg!Jid03qB14g>IIFm#_=DmtB|r(Tgj5n&l2npb(pNH6GFCEGGFP%xa#C_v@>KFx z@>L2~!YdJ!qLfmV(v&ikGL`lz9Z@=_RH;;@#86@?bt|!y`jkeM#+4?Nrj*Vo{igI@ zSxs4AIaoPdd7pBT@=@he$~DS$$|U7RWvVh=nW4;7?p9_g4=bNnzN&mz`IYhq}A=O0G(UN{vdbO1%nAg`vV!=~3xbVXMrmoKyKh<&w(J zDpyqQt2|M8rt(7NjmqyT?^Qmi3aKinYN#5j+NnCLx~jUX`l$M;2B-$9;#Ff+<5ja% zb5(b#7N{1g?o&OXN>*)FrKq;5vQ*is1FA!+BdT20bE-e9URS-TdRz65>I>C3s=uqg zN5jzT(1Pd&{ODn^dkBUdKvvA`WN&Y^zZ2R=nv>W)dbYm zs{v{o)i$fisVS%_tD)62)U?&~)C|>3)Xdc^)d*^ZYGgIG+V^TN)#2&}>KOGX^+fe# z^;GpV^$hiV^#b)m^?mA~`T_Mi^-1+}>bKROslQZzt^QX1o%$adTQy`g zTr}J@JT-hY{4@eIA~m8kqBUYQ;x!UAay0g9lxUP{lxrN-sL-Hlur&rXhBdevJdH_> z?=;S6{G@SD{(u&tg(n`@v)7q((t(B*>TdPoO zpVoe@Vy%9y%i0^Ym9)LJ^R+9rd$bp{pJ{*4f$6N**`TvoM@UCrM_WfnM^{H*$3e$a zCqgGdCs}8QPKHjF&K{kEI%PV?b?S6Fb^3Hhb!K!Hb(VC_>YUfPuJen|U7ZIyPjz1C zyw-W6yG0kStDvi;Yo=?VYprXm>!ur}8?RfWdqTHWm!Zqn9nqcEozq>^UDAE2`-k3o zy-j-JdUAS-dMbKqdb)ZBdd7NYdKP*Pdck_z^mgbK=pE2Is8^NUJFQ>1j@1*akAFH3QU!Z?P|FnLqK2yI( ze@K5?e?k9y{j2&n^l$4w(toP|O#gTNKMVi^X#*t#Qv(Ns?FQ)v`wdDA4jCLaIA(C% z;H1H6gF1r-gC2ukgMI^!!H5CZfM+meFk>)puxN0{;FaMfLpehoLl;AwVV2<`!)C({ zLxy3O;h^EP;dh4T41YGfYTrN zS1oQ>+_Jc1anIs`#T$#a7Qb7(w?tSfTIxd#1ki;6OKVFzOGir=ON?cRCC)O!GSV{2 zG8)Pk9B)}@NwyrcylS=1N&z}jaI$i>a<}rb^0o4}!dpdJ5v-!D60MS~Qmpn{9k!~p zI$?Fns>X_J^}W?it0z{^tzKEZv3h6q!5U^QVl8Hkw3e`zvX-@$w^p<^w~n(ewx(J4 zTXU>OtjDY;tf#EcT3@jK!TKlbE7sSnA6mb)*=!?hBWi=N5x0@Dk+o5 zv9YnYak6o>@v!l>@wM@@iMC0w$+6jQbJ*sTO|4D44a26(hGjEgGh{PrGiGza=7!B} zn_q42+dQ&)V)NeSqpg6gpzTK6&9)M@(zbH8inc1YXj>y&Q(FsLYg;>82U|be2wQ^f zHrp86c-utV9NS#mJllNRO4|lovMt57&6Z}{VcTarY|FLf*-qKc*q*oj(e@|X%eFUc zZ`=NAd(Za0ouD1uPQp&gPR35rPQ^~mPQ%X3&fd<)F3=8R7i<@97ikw|7j2hqS7=vm zS7BFWS8Z2kN3v_QBil{d-LO}*H?g<1cd&P|cd-w&Pq0t6-(jC&pJktGpKrg%exLn* z`x5&@_D%N9_O15q_H_GB`)>PQ`+j?l{jmM0{h0kl`xg$v4$=;W4lxcn4%H4l4vP*q z9iBJ}I0`y$bll=7;wa{bbd+$Ec9e58a5Q!_bF_4{akO`Ia&&d{aP)Tcbv)$Q>bUIq z)=9@H*y)hdq|@)t8=Qrlw>ra}C7h+5<(w6qb)5B_4V;agvz&{Z_d6FmmpacoUv&P# z`A6rUoPTw`>wM4of%6;ZKb-$`S?99RWs8f5i>Qm7i-L=ii>gbqOP))X9H@F+pP0mfxO~noEX69z$X6u~FK>vijQ8+4m?n{}Ia z`_5g`9qq2}uIaAhzQ?`Hz0&=J`ziNYcd~nnd%HW`o#B4Q{jrCbhqQ;BhoXnFhpmU3 zho^^+hrb8TBg`YhBhzD_$3c%Wk0Txx9;ZBNJnB6fJ;)w(k4}$nk6w=vk5P{?j~_g4 zd%W;??eW&*y~jsS0Z&2Cjh+&o(w=gjik>Q-YMz>&I-Yu-)}FSW_MT3jxt<3+OFThikn^~~#q*DJ3# z-n!ma-qzkW-ge&J-hSSJ-dOKY?=bId??Ue)?*rZ?-gVwg??LZj?@@2AH_v;@`>gjb z-oJVO?)`@k;Iqj`$Oq-4eJ!F^y%^G^Xd0F z=kv4ARi7I^w|w6Cyz}|P=YucIcfGHeud1(_ucoh=ucfbzuf4C6uZwS#Z=!FCZ<=q0 zZ;9_A-*Vr>zQ=rz`!@QLeOr8cd|AG|zHHwK-$~yo-x=TYzSn(k`QGup=ljt2iSKjY zkA4Dvf_@wQw)lzoiTNS?6#bO_RQ=TbGW-ht_W6N+#eQe}e)7BQch&EPzox&LzlFb* zzpa0f|55)6|Kt8A{V)69^MBy~$p2}8Re*ayP(W}1E+8U+5U?#EB_J(eXFyg!Za{uO zLBQ#Np@27mLV@T&y+Ff2lR&dT$3VZp;6PkpL?9tBHZUPDIdDf{dSF=~IdC*^A#gG9 zyTGNup93!kUJ1MwcsKA>;D;bs(E6YaL7RgRLE=GDL9#&#K}tb7K^8$lLA!#$pkqO& zgX)4Bf|`OlgP1{GK|Mj-p!uN1pff?sL1%-`2VD)i5p+A~PSD+;2SJa5UI)DmdLQ%w zvk4=Pk;5oplrSn7EsQS40Aq|X#W-P{F)kQ438$o`OukjjwbAtypeA@q>mkp2)($VkXo$Xv)`$WqAJkP9If zLvDmT3f&N@7iu2r8tNAs7>W%I3EdXDBQz~EJ#=U2-q5np!=Xn*D?*Qlo(OFWZ4PY> zZ4ae|GD4Z51EE8qqoHG=3!y)RUJbn-dNcHP=!4M5q0d5JhQ7wF!>z{&;x^!rI22AC zCy7(S>EX4YO2ewcn!?({=wY2<-C+Y^ zLt&#~<6#qFm&0C!Zw;3XmkXByeJ-l7spHCW$_Al zWjq?Mf!D_C;SKR7cyqiJ-WKnGcgDNnJ@Gzxe|!)=7>~n8;0gF`_!xXVJ_(9~gG>f#1w28Ejbc%G1^oaD1^otCP#72fjhDSz55+kD{ zVO^BaRYHPQJ5%7L=eS^Qbbvz0#TWWCTbA1iF!msq6yKQXhpOoIuMgim3~_~co_LXXiFlcKmH2@8i1>u~JZfju z-l(Fe{ZS>`Hf%#~6W=DeO=erjw(Z+ux5aNuidK%+kB0b;qRpa*qvxU*qQ8q?-tNC0 zzn!o>YWw!>x3)js{(Sq(?Z3t3#vF(#i7AaKk6DcQG3KY3%Q4qtm1Ff|4P%XC&0=$7 z55$(lmd2LH3B^gp$;8RUDaJL$b;fnY^~CkXy^ROrH^y&{7mg2zkBleAZ;OwKza9T9 z{zd$&_%{iz2|)?igph==gwqMF3GE5A1V+M>g!c&_68=nFm*@pKa>EiM5($Y{6CWf# zN_>*|JSiioFlk>Bm{goJm-Kzo4@sAjE+^xY6O)sZQ+GN^F+PSm~X+NY_r&H2f)7#VO8O9k78BQ54 z8SWXg85c4xX8f4(^UjE!2|JT^rtD11RLeBZG|e>6w94F@c{uZEW<_RI=8eq9nNKsH zXTHiZ$#Tqc&T`H2$ePc(nDt}UPgz&8BeRpTQ?hqtXJq$hPi9YN&t@;=Y{)_8i04S= z$mB%jq~@gMWaMP!8s^&NI^;U#y5=6rtFyq>(?y#Bm*yEg9Hyh~`;)?F35NV^($k$1J^tL7W#8|RzmTjUSsPv_6(&*y)) z+h=#!?ugxyyQ6lu?C#m!yPLgxu)w0gwZOf=v%sg|yMjvvKNnmnxV|T9PwJkuJsEql z_OSO%?3vm#vuD1rtngG}O<`SOL*f0x-wNLrzAOB&7u&{fP1lEF}xUAEM6>C z98sK5oK&1roL2m__>ba`C9sn9CB-GjOHPzjm(-M8D!Ef~x8#1wqk~ol-4A*m^gif! zaOvRB2d^BwcJOAYO{r(8cd2h_KUIF@znWd*EaU4>x9#tLl3wus@Eshoe(^+;l$<>F()!kWS+=Ak$2+FNx_pFPHs9W zbdq;+>Ez1Eb0@#AMpY|Ut5&O3YgW^%2dX*M!`0kVcBi~g`JVDW6?E#{scWZhoVs=D z&guBmS*LSO=bhet`d!V&n$0yrHCt=^Y9eYPYlt<`HO!jfn$eoEnu%JcTK`&HZA2}h zc3W*sZE9_LZDwtDZC-7D?aA7a+J)NZweRZI*KMhjtW&B}ty8bls?)7AtFx@Lsk5td ztaGmOsSB%%t4piPsLQO&uFI|4SGT{eq^`8CyzWR{bseRSSvOoaRX1C=PdOeUZY;S-lX2V-m2cF-oD#x?|sJ~VJu>KWE zfV71qLJ}h(NfIPQk_t(Uq(Raq>5|My4kTYv2q}z&ClN`}q-4?#QU)oLltaoR6_LtF zr$|ktW>O2OjYK8&korgiBo1kW#3jv=R!CP!cSv_h_el>)k4e9gekc7w`q&`Qu)aaK zL83vqLBGMG!K%Tg!LGrf!Lz}q!M`D}0oxGLKx{~8$ZjZXC~7#+aIm4Qp{k*}p{Aj( zp`oFvVYuOD!_!8|Mx{pWMvF$rM(0M?M)yX~#-PUFMqFcfV`L+-F`+S|F~9L(V?|?C zV|8OqV|`<5Bek)kv9qzek=1yn@nw@hlU9>)lTDLrQ&3Yx6QOBaQ%qBQQ+iWoQ%+M} z)9$7{O(jjon(CWco2X44P0XgArlF?MrtzkUrs<~Hrj@3jn|^6}-t?~NkEV}g7 zDw$61CHIp#Iz^M>R(`CpTv`7d4kPA8D>=u4=AsZftIDZf$OFrZ+R1XPX~4 zzo)2DbSY*Odx{Swn1Z83PzaQ5lq59EzNcKJJfOU!{6_hm@(1P5mQ5`}En8c}T97T`Eeb6fEru<2E$%H|Exs)Q zEtnR33$Z1-C8j05C9!2^OF_$_mg6laTTZvswKTM}x6oTUTe@0UEqyJcEi*04Etgw< zX}R0-pyhGPvzFgm{%HBLRiIU{bwjIit7mIy>)zJV*2>m~*7jCsaeV z>s0G(>wN3E)}LGNwmxfp+4@`S@2!8d0d1SwgxW;f#M%&TvTf)#-8QQ>=Qg)C&o-Yn z|2AA(L>r+ks%?8)Y}<~u+_nR4N7|0HRkl^Noos7pBe%7*wYAaOI@-INP7C*IR7BokiA1#y?PK%^P(YDi4Xlb;av@BXKZ5Ius9j2Y6HPbq1Oj-}E zk2XLXr%lpkXmhkh+8NqK+I89^+H2Zd+I!kZx&VC(U4$-1N6^LTl5{1y7TuigKzF9Q z(LL!t^k6!U9zlVcp@<;ojlZ;oA|=5!Qk4Aa-o)i0O#y zNb4x*IM{KbgVfQ~LFs7gpmnf1*d2o%Lmi_XV;yrHXFGoC__gC{$McSt9j`myFkp=J zj17!U3?YUHLxQ2oFl1OUY#9y=XNDWYpAp0eW`r`r8F)r4BaKnOC}xy0${9x)m5f>j ziP6MpX0$Te8QlynW07%z@dM*0#udhO#y!SE#uLUf#!JR)#>dXhoybn5POVPePJ>S4 zPP0z?PNz=SPWMi)PM^-;PC{pLXI5u!XMX3N&V8MSIuCaq>#XcN(OKQu*vaVRbWU_m zcg}S#b}n`P(D_s6mCkFOH#={4KI;6Pxq&IlL@>pfQcPJUnyJCmX6iBxm_|%%rVBHW z8P1GkMlrWD8aImDb~&M@bhi_9hF3iA^4 zCi4mN4f7rILl>-TeV1^TXcwXj)g{>_-KEl{(`DJ^)aBac(dFIc*A?0o-WAzJ?27J+ z=}PU&>Du3Qxa(Ng@vf6yr@P2qEnV$hv@S*$vumJhyz5NY#jZcqh(rer6(Cggm z*6Z0D)EnH3>kaRX>?QUl^k(!H_8#ml>pjw2(OcD9-`m*R+}qOI-b?Fc^^Ww;_nzzh zzW2x8pL?(N{@Q!L_fhYY-sin9d;jR$(1+}k>r?1c>Qm`M_v!T+_L=mV^;z~=_qp`> z^+oi>^u_lj^`-Wu_wDK{=-b;@)OVn-r0-Z?eP3H&Z{J8Cw{N^}qHn72Oy5f1`M&S_ ze(d|H?^fTVzSrz^Y$5hmHk^%OOR|;Ns%&+(CR>NC$2MoXvV+(WYyx{5JBA(4PG@Jb zbJ%(8-RwQ=68156J-e0N&Zeup`d|0I?SJ3@VE{I;Za`=NHK078 zJ)k#WIAAhhKHxCmJm5CqG2lJmI}kFkZ6J9dZ=h&k|3LA;!GS{q#|KUhoF1qhAPqDQ zPzQPjxC65T3j=2cRtC-wTpqYKaC6}Hz^?=M2A&VR8{9Z3Hi#UQ7?d8A8&n(A9Ml=q z8#Ej=9<&*B8^jFa2ML2wgVBRAgQdF3IZ7NAjt<9^inE;)$4THMb5c1uoV}dGoRgf>oH|Yehs>dKIyv1O7KhCl;EZz?I6rW%b8d2O zbMA2Na-MNsa(?5y<-FtkF$4^49g-bF52+7n4rveR4w(&E4%rOZ4LJ@u5BUrQ4-tlv zhq8uphw_K^4DA~_G<10A*ihxriJ|JD#v$qudx$&48=4xL9awVUG-s@J6OaW=G~nz8hH@`C;Vx$fJ?hBX352A9+9WVRXaj=279%t)uW!H4!%c$>Yz$j)kcoa7pKDvE0eY9Zo(CG2elcT3c>qZ+!+ehi6ougf& ztkJ&F(b4(Q^P|^C?~OhjeKPud^wsDGE{waL3vf4aw{VeMMXom2g6qh2;kt9ZxW3#F zZWtHOC2+TKw{ugt*<6rY&OOSl8{FI6U%B_W zkGQY7Z@KTeAI4x~>&Aq}P-F6A+G8eT=3`c4wqp)so?|{^{$qh-*s+i?;#lHX&e+~D zaIAQ&bgX>r#Mr5^+Ohhv#xe33eQa=SYV7RTPh*$Iu8v(FyE*n??D5#Mu@_^n$KH&? z#)Zcv##P64$Mwez$BoBL$L+=)$6dzV#y!Tp#xdjg@x<|+g9*!5mBk;EIl6a}Sbly&0HZPaAk9UYy&1>W}^ICaSUI(v_ zH^3X>jqt{JJl+EDJnuU19`7OV3GX@Y74O3YY-0TcFtKT3%LH;lX+nF#V#0C4Wx{>J zYr=OTWFl+=KS7w-HnDvoWg>f`aH4$T#KftI+6mG`(*$jTG0`>AGtoEEKQT5jKXGy5 z=ZUKmHzw{(+?#kf@p$6h#2*tMCj};LCOs#;Cw(UaCd(#IO`e{tnXH?nPSPgnlZ;8u zoQr&FfWrn9DVr}L)^rpu;D)4b`0=^v&q zPhXq9IsNPO{pm;3Pp03@2+xSmNX^L3$j|7{SkBnY*v~l4xXx70)XuceFlM@DSTh4N zLo=f@V>2@|XJ&5BJeb`w3!gQb^_UHw4Vev{4V#UdO`J`cO`F|0n>AZByMMNLwsdxS z_T23G*$cB5XK&5knY}mraQ4aUv)PYxLUU4cI&=DSMsucf7IW5fc5{w%E_3d4;d7C5 z#JO#A+vnou66aFp(&jSeGUxWpmCljpI_IY5F3mlj2j&&#wdVuoL+5ex@$*^pyXOn% zi{=l^m(L%aube+IUp+rRe|!Fq1z2#i+%^#gxTeizSOk7AqF37ONLYi%pA^#kNK2V$b5>B5%oH$#}_O$#*GW z39}Tk6t;w4A}*yYr7dMFWi90{?ONKsv}dV#X?E$^vdpsTvhK3!vfZ-dve$Cpa>#P* za{O}oa^`aOa_;hh()L|%(c%?|36#%e*lDe BN<07n literal 42522 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 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; }