mirror of
https://github.com/objective-see/TaskExplorer.git
synced 2026-03-22 07:02:39 +00:00
v2.2.0 friends updates removed error reporting removed old code (pre-macOS 11) ...more changes / improvements soon!
491 lines
12 KiB
Objective-C
491 lines
12 KiB
Objective-C
//
|
|
// File.m
|
|
// TaskExplorer
|
|
//
|
|
// Created by Patrick Wardle on 2/19/15.
|
|
// Copyright (c) 2015 Objective-See. All rights reserved.
|
|
//
|
|
|
|
#import "Binary.h"
|
|
#import "Consts.h"
|
|
#import "Signing.h"
|
|
#import "Utilities.h"
|
|
#import "AppDelegate.h"
|
|
|
|
@implementation Binary
|
|
|
|
@synthesize path;
|
|
@synthesize name;
|
|
@synthesize icon;
|
|
@synthesize bundle;
|
|
@synthesize hashes;
|
|
@synthesize parser;
|
|
@synthesize vtInfo;
|
|
@synthesize isPacked;
|
|
@synthesize loadedIn;
|
|
@synthesize notFound;
|
|
@synthesize isEncrypted;
|
|
@synthesize signingInfo;
|
|
@synthesize isTaskBinary;
|
|
|
|
//init method
|
|
-(id)initWithParams:(NSDictionary*)params
|
|
{
|
|
//super
|
|
// ->saves path, etc
|
|
self = [super initWithParams:params];
|
|
if(self)
|
|
{
|
|
//since path is always full path to binary
|
|
// ->manaully try to find & load bundle (for .apps)
|
|
self.bundle = findAppBundle(self.path);
|
|
|
|
/* now we have bundle (maybe), try get name and icon */
|
|
|
|
//get task's name
|
|
// ->either from bundle or path's last component
|
|
self.name = [self getName];
|
|
|
|
//get task's icon
|
|
// ->either from bundle or just use system icon
|
|
self.icon = [self getIcon];
|
|
|
|
//is in dyld cache
|
|
self.inCache = isInSharedCache(self.path);
|
|
|
|
//determine if its on disk
|
|
// though ignore files in dyld cache
|
|
if(YES != self.inCache)
|
|
{
|
|
//set
|
|
self.notFound = ![[NSFileManager defaultManager] fileExistsAtPath:self.path];
|
|
}
|
|
|
|
//get attributes
|
|
self.attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.path error:nil];
|
|
}
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return self;
|
|
}
|
|
|
|
//machO parse
|
|
-(BOOL)parse
|
|
{
|
|
//flag
|
|
BOOL wasParsed = NO;
|
|
|
|
//sync
|
|
@synchronized(self)
|
|
{
|
|
//alloc parser
|
|
if(nil == parser)
|
|
{
|
|
//alloc macho parser iVar
|
|
parser = [[MachO alloc] init];
|
|
|
|
//parse
|
|
if(YES != [self.parser parse:self.path classify:YES])
|
|
{
|
|
//unset parser
|
|
self.parser = nil;
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
//unset 'packed' flag for apple signed binaries
|
|
// ->as apple doesn't pack binaries, but packer algo has some false positives
|
|
if( (noErr == [self.signingInfo[KEY_SIGNATURE_STATUS] intValue]) &&
|
|
(Apple == [self.signingInfo[KEY_SIGNATURE_SIGNER] intValue]) )
|
|
{
|
|
//unset
|
|
self.parser.binaryInfo[KEY_IS_PACKED] = nil;
|
|
}
|
|
|
|
}//sync
|
|
|
|
//happy
|
|
wasParsed = YES;
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return wasParsed;
|
|
}
|
|
|
|
//get task's name
|
|
// ->either from bundle or path's last component
|
|
-(NSString*)getName
|
|
{
|
|
//name
|
|
NSString* taskName = nil;
|
|
|
|
//try to get name from bundle
|
|
// ->key 'CFBundleName'
|
|
if(nil != self.bundle)
|
|
{
|
|
//extract name
|
|
taskName = [self.bundle infoDictionary][@"CFBundleName"];
|
|
}
|
|
|
|
//no bundle/ or bundle lookup failed
|
|
// ->just use last component of path
|
|
if(nil == taskName)
|
|
{
|
|
//special case
|
|
// ->kernel -> 'kernel_task'
|
|
if(YES == [self.path isEqualToString:path2Kernel()])
|
|
{
|
|
//set kernel
|
|
taskName = @"kernel_task";
|
|
}
|
|
//default
|
|
// ->name is just last component of path
|
|
else
|
|
{
|
|
//extract name
|
|
taskName = [self.path lastPathComponent];
|
|
}
|
|
}
|
|
|
|
return taskName;
|
|
}
|
|
|
|
//get an icon for a process
|
|
// ->for apps, this will be app's icon, otherwise just a standard system one
|
|
-(NSImage*)getIcon
|
|
{
|
|
//icon's file name
|
|
NSString* iconFile = nil;
|
|
|
|
//icon's path
|
|
NSString* iconPath = nil;
|
|
|
|
//icon's path extension
|
|
NSString* iconExtension = nil;
|
|
|
|
//icon
|
|
NSImage* taskIcon = nil;
|
|
|
|
//for app's
|
|
// ->extract their icon
|
|
if(nil != self.bundle)
|
|
{
|
|
//get file
|
|
iconFile = self.bundle.infoDictionary[@"CFBundleIconFile"];
|
|
|
|
//get path extension
|
|
iconExtension = [iconFile pathExtension];
|
|
|
|
//if its blank (i.e. not specified)
|
|
// ->go with 'icns'
|
|
if(YES == [iconExtension isEqualTo:@""])
|
|
{
|
|
//set type
|
|
iconExtension = @"icns";
|
|
}
|
|
|
|
//set full path
|
|
iconPath = [self.bundle pathForResource:[iconFile stringByDeletingPathExtension] ofType:iconExtension];
|
|
|
|
//load it
|
|
taskIcon = [[NSImage alloc] initWithContentsOfFile:iconPath];
|
|
}
|
|
|
|
//process is not an app or couldn't get icon
|
|
// ->try to get it via shared workspace
|
|
if( (nil == self.bundle) ||
|
|
(nil == taskIcon) )
|
|
{
|
|
//extract icon
|
|
taskIcon = [[NSWorkspace sharedWorkspace] iconForFile:self.path];
|
|
}
|
|
|
|
return taskIcon;
|
|
}
|
|
|
|
//get signing info (which takes a while to generate)
|
|
// ->this method should be called in the background
|
|
-(void)generatedSigningInfo
|
|
{
|
|
//set signing info
|
|
self.signingInfo = extractSigningInfo(1, self.path, kSecCSCheckAllArchitectures | kSecCSCheckNestedCode | kSecCSDoNotValidateResources);
|
|
|
|
return;
|
|
}
|
|
|
|
//get detailed info (which takes a while to generate)
|
|
// ->only shown to user if they click 'info' so this method should be called in the background
|
|
-(void)generateDetailedInfo
|
|
{
|
|
//grab file attributes
|
|
self.attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.path error:nil];
|
|
|
|
//computes hashes
|
|
// ->set 'md5' and 'sha1' iVars
|
|
self.hashes = hashFile(self.path);
|
|
|
|
return;
|
|
}
|
|
|
|
//format the signing info dictionary
|
|
-(NSString*)formatSigningInfo
|
|
{
|
|
//pretty print
|
|
NSMutableString* prettyPrint = nil;
|
|
|
|
//sanity check
|
|
if(nil == self.signingInfo)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//switch on signing status
|
|
switch([self.signingInfo[KEY_SIGNATURE_STATUS] integerValue])
|
|
{
|
|
//unsigned
|
|
case errSecCSUnsigned:
|
|
{
|
|
//set string
|
|
prettyPrint = [NSMutableString stringWithString:@"unsigned"];
|
|
|
|
//brk
|
|
break;
|
|
}
|
|
|
|
//errSecCSSignatureFailed
|
|
case errSecCSSignatureFailed:
|
|
{
|
|
//set string
|
|
prettyPrint = [NSMutableString stringWithString:@"invalid signature"];
|
|
|
|
//brk
|
|
break;
|
|
}
|
|
|
|
//happily signed
|
|
case STATUS_SUCCESS:
|
|
{
|
|
//init
|
|
prettyPrint = [NSMutableString string];//stringWithString:@"signed by:"];
|
|
|
|
//add each signing auth
|
|
for(NSString* signingAuthority in self.signingInfo[KEY_SIGNATURE_AUTHORITIES])
|
|
{
|
|
//append
|
|
[prettyPrint appendString:[NSString stringWithFormat:@"%@, ", signingAuthority]];
|
|
}
|
|
|
|
//remove last comma & space
|
|
if(YES == [prettyPrint hasSuffix:@", "])
|
|
{
|
|
//remove
|
|
[prettyPrint deleteCharactersInRange:NSMakeRange([prettyPrint length]-2, 2)];
|
|
}
|
|
|
|
//brk
|
|
break;
|
|
}
|
|
|
|
//unknown
|
|
default:
|
|
|
|
//set string
|
|
prettyPrint = [NSMutableString stringWithFormat:@"unknown (status/error: %ld)", (long)[self.signingInfo[KEY_SIGNATURE_STATUS] integerValue]];
|
|
|
|
//brk
|
|
break;
|
|
}
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return prettyPrint;
|
|
}
|
|
|
|
//override method
|
|
// ->hash
|
|
-(NSUInteger)hash
|
|
{
|
|
return [self.path hash];
|
|
}
|
|
|
|
//override method
|
|
// ->equality check
|
|
-(BOOL)isEqual:(id)object
|
|
{
|
|
//flag
|
|
BOOL objEqual = NO;
|
|
|
|
//check self
|
|
if(self == object)
|
|
{
|
|
//match
|
|
objEqual = YES;
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//check for type
|
|
if(YES != [object isKindOfClass:[Binary class]])
|
|
{
|
|
//no match
|
|
objEqual = NO;
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//do check
|
|
if(YES == [((Binary*)object).path isEqualToString:self.path])
|
|
{
|
|
//happy
|
|
objEqual = YES;
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return objEqual;
|
|
}
|
|
|
|
//convert object to JSON string
|
|
-(NSString*)toJSON
|
|
{
|
|
//json string
|
|
NSMutableString *json = nil;
|
|
|
|
//json data
|
|
// for intermediate conversions
|
|
NSData *jsonData = nil;
|
|
|
|
//hashes
|
|
NSString* fileHashes = nil;
|
|
|
|
//signing info
|
|
NSString* fileSigs = nil;
|
|
|
|
//VT detection ratio
|
|
NSString* vtDetectionRatio = nil;
|
|
|
|
//tasks loaded in
|
|
NSMutableArray* taskPids = nil;
|
|
|
|
//'loaded in' list
|
|
NSString* tasks = nil;
|
|
|
|
//init file hash to default string
|
|
// used when hashes are nil, or serialization fails
|
|
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)
|
|
{
|
|
//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)
|
|
{
|
|
;
|
|
}
|
|
}
|
|
|
|
//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)
|
|
{
|
|
;
|
|
}
|
|
}
|
|
|
|
//dylibs
|
|
//covert 'loaded in' array to JSON
|
|
if(YES != isTaskBinary)
|
|
{
|
|
//init
|
|
taskPids = [NSMutableArray array];
|
|
|
|
//tasks loaded in
|
|
for(Task* task in self.loadedIn)
|
|
{
|
|
//add pid
|
|
[taskPids addObject:task.pid];
|
|
}
|
|
|
|
//wrap since we are serializing JSON
|
|
@try
|
|
{
|
|
//convert
|
|
jsonData = [NSJSONSerialization dataWithJSONObject:taskPids options:kNilOptions error:NULL];
|
|
if(nil != jsonData)
|
|
{
|
|
//convert data to string
|
|
tasks = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
}
|
|
}
|
|
//ignore exceptions
|
|
// ->file sigs will just be 'unknown'
|
|
@catch(NSException *exception)
|
|
{
|
|
;
|
|
}
|
|
}
|
|
|
|
//init VT detection ratio
|
|
vtDetectionRatio = [NSString stringWithFormat:@"%lu/%lu", (unsigned long)[self.vtInfo[VT_RESULTS_POSITIVES] unsignedIntegerValue], (unsigned long)[self.vtInfo[VT_RESULTS_TOTAL] unsignedIntegerValue]];
|
|
|
|
//init json
|
|
json = [NSMutableString stringWithFormat:@"\"name\": \"%@\", \"path\": \"%@\", \"hashes\": %@, \"signature(s)\": %@, \"VT detection\": \"%@\", \"encrypted\": %d, \"packed\": %d, \"deleted\": %d", self.name, self.path, fileHashes, fileSigs, vtDetectionRatio, self.isEncrypted, self.isPacked, self.notFound];
|
|
|
|
//dylibs
|
|
// add tasks they are loaded in
|
|
if(YES != self.isTaskBinary)
|
|
{
|
|
//add
|
|
[json appendString:[NSString stringWithFormat:@", \"loaded in\": \"%@\"", tasks]];
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
@end
|