Files
BlockBlock/Application/Application/VirusTotalViewController.m
T
Patrick Wardle 397699c1d0 switched to universal logging
remove old log file #74
switch to os_log_* APIs
2024-03-19 20:12:11 -10:00

279 lines
6.7 KiB
Objective-C

//
// file: VirusTotalViewController.h
// project: BlockBlock (login item)
// description: view controller for VirusTotal results popup (header)
//
// created by Patrick Wardle
// copyright (c) 2017 Objective-See. All rights reserved.
//
#import "utilities.h"
#import "VirusTotal.h"
#import "VirusTotalViewController.h"
/* GLOBALS */
//log handle
extern os_log_t logHandle;
@implementation VirusTotalViewController
@synthesize message;
@synthesize vtSpinner;
//automatically invoked
// configure popover and kick off VT queries
-(void)popoverWillShow:(NSNotification *)notification;
{
//set message
self.message.stringValue = @"querying virus total...";
//bg thread for VT
[self performSelectorInBackground:@selector(queryVT) withObject:nil];
return;
}
//automatically invoked
// finish configuring popover
-(void)popoverDidShow:(NSNotification *)notification
{
//show spinner
self.vtSpinner.hidden = NO;
//start
[self.vtSpinner startAnimation:nil];
//make the animation show...
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
return;
}
//make a query to VT in the background
// invokes helper function to update UI as needed (results/errors)
-(void)queryVT
{
//vt object
VirusTotal* vtObj = nil;
//hash
NSString* hash = nil;
//item
NSMutableDictionary* item = nil;
//alloc
vtObj = [[VirusTotal alloc] init];
//alloc
item = [NSMutableDictionary dictionary];
//nap to allow msg/spinner to do a bit
[NSThread sleepForTimeInterval:1.0f];
//hash
hash = hashFile(self.itemPath);
if(0 == hash.length)
{
//err msg
os_log_error(logHandle, "ERROR: failed to hash %{public}@ to submit", self.itemName);
//show error on main thread
dispatch_sync(dispatch_get_main_queue(), ^{
//show error
[self showError];
});
//bail
goto bail;
}
//dbg msg
os_log_debug(logHandle, "querying VT with %{public}@", self.itemPath);
//add name
item[@"name"] = self.itemName;
//add path
item[@"path"] = self.itemPath;
//add hash
item[@"hash"] = hash;
//query VT
// ->also check for error (offline, etc)
if(YES == [vtObj queryVT:item])
{
//modal window, so use 'performSelectorOnMainThread' to update
[self performSelectorOnMainThread:@selector(displayResults:) withObject:item waitUntilDone:YES];
}
//error
else
{
//err msg
os_log_error(logHandle, "ERROR: failed to query virus total: %{public}@", item);
//modal window, so use 'performSelectorOnMainThread' to update
[self performSelectorOnMainThread:@selector(showError) withObject:nil waitUntilDone:YES];
}
bail:
return;
}
//display results
-(void)displayResults:(NSMutableDictionary*)item
{
//dbg msg
os_log_debug(logHandle, "VT response: %{public}@", item);
//stop spinner
[self.vtSpinner stopAnimation:nil];
//hide spinner
self.vtSpinner.hidden = YES;
//editable (for link)
self.message.allowsEditingTextAttributes = YES;
//selectable (for link)
self.message.selectable = YES;
//format response
// updates self.message
[self formatVTInfo:item];
return;
}
//show error in UI
-(void)showError
{
//stop spinner
[self.vtSpinner stopAnimation:nil];
//hide spinner
self.vtSpinner.hidden = YES;
//set message
self.message.stringValue = @"failed to query virus total :(";
return;
}
//set's string with process/binary name + signing info
// extra logic need to make a clickable link for VT report
-(void)formatVTInfo:(NSDictionary*)item
{
//info
NSMutableString* info = nil;
//name
// handles truncations, etc
NSString* name = nil;
//coverted color
NSColor *convertedColor = nil;
//hex color
NSString* hexColor = nil;
//html code
NSString* html = nil;
//init string
info = [[NSMutableString alloc] initWithString:@""];
//grab name
name = item[@"name"];
//truncate long names
if(name.length > 25)
{
//truncate
name = [[name substringToIndex:22] stringByAppendingString:@"..."];
}
//add name
[info appendFormat:@"%@: ", name];
//sanity check
if( (nil == item[@"vtInfo"]) ||
(nil == item[@"vtInfo"][@"found"]) )
{
//set
[info appendString:@"received invalid response"];
//add
self.message.stringValue = info;
//bail
goto bail;
}
//add ratio and report link if file is found
if(0 != [item[@"vtInfo"][@"found"] intValue])
{
//sanity check
if( (nil == item[@"vtInfo"][@"detection_ratio"]) ||
(nil == item[@"vtInfo"][@"permalink"]) )
{
//set
[info appendString:@"received invalid response"];
//add
self.message.stringValue = info;
//bail
goto bail;
}
/*
//make ratio red if there are positives
if( (nil != item[@"vtInfo"][@"positives"]) &&
(0 != [item[@"vtInfo"][@"positives"] intValue]) )
{
//red
//attributes = @{NSFontAttributeName: [NSFont fontWithName:@"Menlo" size:13], NSForegroundColorAttributeName:[NSColor systemRedColor]};
}
*/
//add ratio
[info appendFormat:@" %@", item[@"vtInfo"][@"detection_ratio"]];
//convert color
convertedColor = [self.message.textColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
//hexify for html
hexColor = [NSString stringWithFormat:@"%02X%02X%02X", (int) (convertedColor.redComponent * 0xFF), (int) (convertedColor.greenComponent * 0xFF), (int) (convertedColor.blueComponent * 0xFF)];
//create html
html = [NSString stringWithFormat:@"<span style=\"font-family:'%@'; font-size:%dpx; color:%@\">%@<br><center><a href=\"%@\">Full Details</a></center></span>", self.message.font.fontName, (int)self.message.font.pointSize, hexColor, info, item[@"vtInfo"][@"permalink"]];
//set message
// note: attribute string
self.message.attributedStringValue = [[NSAttributedString alloc] initWithHTML:[html dataUsingEncoding:NSUTF8StringEncoding] documentAttributes:nil];
}
//file not found on vt
else
{
//add ratio
[info appendString:@"not found"];
//add
self.message.stringValue = info;
}
bail:
return;
}
@end