532 lines
14 KiB
Objective-C
532 lines
14 KiB
Objective-C
//
|
|
// Enumerator.m
|
|
// cameraUsers
|
|
//
|
|
// Created by Patrick Wardle on 9/9/16.
|
|
// Copyright (c) 2016 Objective-See. All rights reserved.
|
|
//
|
|
|
|
#import "main.h"
|
|
#import "Consts.h"
|
|
#import "Logging.h"
|
|
#import "Utilities.h"
|
|
#import "Enumerator.h"
|
|
|
|
#import <libproc.h>
|
|
#import <sys/sysctl.h>
|
|
|
|
//ignored mach sender procs
|
|
// TODO: maybe just ignore apple signed daemons/bg procs!?
|
|
static NSArray* ignoredProcs = nil;
|
|
|
|
@implementation Enumerator
|
|
|
|
@synthesize machSenders;
|
|
@synthesize videoActive;
|
|
|
|
//init
|
|
-(instancetype)init
|
|
{
|
|
//init
|
|
if(self = [super init])
|
|
{
|
|
//alloc dictionary
|
|
machSenders = [NSMutableDictionary dictionary];
|
|
|
|
//init ignored procs
|
|
ignoredProcs = @[
|
|
@"/sbin/launchd",
|
|
@"/usr/libexec/lsd",
|
|
@"/usr/sbin/notifyd",
|
|
@"/usr/sbin/syslogd",
|
|
@"/usr/sbin/cfprefsd",
|
|
@"/usr/libexec/avconferenced",
|
|
@"/usr/libexec/opendirectoryd",
|
|
@"/usr/libexec/UserEventAgent",
|
|
@"/System/Library/CoreServices/launchservicesd",
|
|
@"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/CVMServer",
|
|
@"/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Resources/WindowServer",
|
|
@"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/Support/coreservicesd",
|
|
@"/System/Library/Frameworks/VideoToolbox.framework/Versions/A/XPCServices/VTDecoderXPCService.xpc/Contents/MacOS/VTDecoderXPCService"
|
|
];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
//singleton interface
|
|
+(id)sharedManager
|
|
{
|
|
//instance
|
|
static Enumerator *sharedEnumerator = nil;
|
|
|
|
//once token
|
|
static dispatch_once_t onceToken;
|
|
|
|
//init
|
|
// ->only exec'd once though :)
|
|
dispatch_once(&onceToken, ^{
|
|
|
|
//init
|
|
sharedEnumerator = [[self alloc] init];
|
|
|
|
});
|
|
|
|
return sharedEnumerator;
|
|
}
|
|
|
|
//find 'VDCAssistant' or 'AppleCameraAssistant'
|
|
-(pid_t)findCameraAssistant
|
|
{
|
|
//pid of assistant
|
|
pid_t cameraAssistant = 0;
|
|
|
|
//status
|
|
int status = -1;
|
|
|
|
//# of procs
|
|
int numberOfProcesses = 0;
|
|
|
|
//array of pids
|
|
pid_t* pids = NULL;
|
|
|
|
//process path
|
|
NSString* processPath = nil;
|
|
|
|
//get # of procs
|
|
numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
|
|
|
|
//alloc buffer for pids
|
|
pids = calloc(numberOfProcesses, sizeof(pid_t));
|
|
|
|
//get list of pids
|
|
status = proc_listpids(PROC_ALL_PIDS, 0, pids, numberOfProcesses * sizeof(pid_t));
|
|
if(status < 0)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//iterate over all pids
|
|
// ->get name for each via helper function
|
|
for(int i = 0; i < numberOfProcesses; ++i)
|
|
{
|
|
//skip blank pids
|
|
if(0 == pids[i])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//get name
|
|
processPath = getProcessPath(pids[i]);
|
|
if( (nil == processPath) ||
|
|
(0 == processPath.length) )
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//is 'VDCAssistant'?
|
|
if(YES == [processPath isEqualToString:VDC_ASSISTANT])
|
|
{
|
|
//save
|
|
cameraAssistant = pids[i];
|
|
|
|
//pau
|
|
break;
|
|
}
|
|
|
|
//is 'AppleCameraAssistant'?
|
|
else if(YES == [processPath isEqualToString:APPLE_CAMERA_ASSISTANT])
|
|
{
|
|
//save
|
|
cameraAssistant = pids[i];
|
|
|
|
//pau
|
|
break;
|
|
}
|
|
}
|
|
|
|
//bail
|
|
bail:
|
|
|
|
//free buffer
|
|
if(NULL != pids)
|
|
{
|
|
//free
|
|
free(pids);
|
|
}
|
|
|
|
return cameraAssistant;
|
|
}
|
|
|
|
//forever, baseline by getting all current procs that have sent a mach msg to *Assistant
|
|
// ->ensures its only invoke while camera is not in use, so these are all just baselined procs
|
|
-(void)start
|
|
{
|
|
//baseline forever
|
|
// ->though logic will skip if video is active
|
|
while(YES)
|
|
{
|
|
//sync baselining
|
|
@synchronized(self)
|
|
{
|
|
//only baseline if video isn't active
|
|
if(YES != self.videoActive)
|
|
{
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, @"baselining mach senders...");
|
|
|
|
//enumerate procs that have send mach messages
|
|
self.machSenders = [self enumMachSenders:[self findCameraAssistant]];
|
|
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu baselined mach senders: %@", (unsigned long)self.machSenders.count, self.machSenders]);
|
|
}
|
|
}
|
|
|
|
//nap for a minute
|
|
[NSThread sleepForTimeInterval:60];
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//enumerate all (recent) process that appear to be using video
|
|
-(NSMutableArray*)enumVideoProcs
|
|
{
|
|
//current procs
|
|
NSMutableArray* videoProcs = nil;
|
|
|
|
//mach senders
|
|
NSMutableDictionary* currentSenders = nil;
|
|
|
|
//candidate video procs
|
|
// ->those that have new mach message
|
|
NSMutableArray* candidateVideoProcs = nil;
|
|
|
|
//pid of camera assistant process
|
|
pid_t cameraAssistant = 0;
|
|
|
|
//alloc
|
|
candidateVideoProcs = [NSMutableArray array];
|
|
|
|
//sync this logic
|
|
// ->prevent baselining thread from doing anything
|
|
@synchronized(self)
|
|
{
|
|
|
|
//find 'VDCAssistant' or 'AppleCameraAssistant'
|
|
cameraAssistant = [self findCameraAssistant];
|
|
if(0 == cameraAssistant)
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, @"failed to find VDCAssistant/AppleCameraAssistant process");
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//get procs that currrently have sent Mach msg to *Assistant
|
|
// ->returns dictionary of process id, and number of mach messages
|
|
currentSenders = [self enumMachSenders:cameraAssistant];
|
|
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu current mach senders: %@", (unsigned long)currentSenders.count, currentSenders]);
|
|
|
|
//remove any known/existing senders
|
|
for(NSNumber* processID in currentSenders.allKeys)
|
|
{
|
|
//add any candidate procs
|
|
// ->those that have new mach message
|
|
if( [currentSenders[processID] intValue] > [self.machSenders[processID] intValue])
|
|
{
|
|
//ignore client/requestor
|
|
if(clientPID == processID.intValue)
|
|
{
|
|
//ignore
|
|
continue;
|
|
}
|
|
|
|
//add
|
|
[candidateVideoProcs addObject:processID];
|
|
}
|
|
}
|
|
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu candidate video procs: %@", (unsigned long)candidateVideoProcs.count, candidateVideoProcs]);
|
|
|
|
//update
|
|
self.machSenders = currentSenders;
|
|
|
|
//invoke 'sample' to confirm that candidates are using CMIO/video inputs
|
|
// ->note, will skip FaceTime.app on macOS Sierra, as it doesn't do CMIO stuff directly
|
|
videoProcs = [self sampleCandidates:candidateVideoProcs];
|
|
|
|
}//sync
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return videoProcs;
|
|
}
|
|
|
|
//get procs that currrently have sent Mach msg to *Assistant
|
|
// ->returns dictionary of process id, and number of mach messages
|
|
-(NSMutableDictionary*)enumMachSenders:(pid_t)cameraAssistant
|
|
{
|
|
//senders
|
|
NSMutableDictionary* senders = nil;
|
|
|
|
//results from 'lsmp' cmd
|
|
NSString* results = nil;
|
|
|
|
//substrings
|
|
NSArray* subStrings = nil;
|
|
|
|
//process id
|
|
NSNumber* processID = nil;
|
|
|
|
//process path
|
|
NSString* processPath = nil;
|
|
|
|
//alloc
|
|
senders = [NSMutableDictionary dictionary];
|
|
|
|
//exec 'lsmp' w/ pid of camera asssistant to get mach ports
|
|
results = [[NSString alloc] initWithData:execTask(LSMP, @[@"-p", @(cameraAssistant).stringValue]) encoding:NSUTF8StringEncoding];
|
|
if( (nil == results) ||
|
|
(0 == results.length) )
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//parse results
|
|
// ->looking for (<pid>) process name
|
|
for(NSString* line in [results componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\n"]])
|
|
{
|
|
//skip blank lines
|
|
if(0 == line.length)
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//parse on '()'
|
|
subStrings = [line componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"()"]];
|
|
if(subStrings.count < 3)
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//skip 'unknown' processes
|
|
// output looks like "(-) Unknown Process"
|
|
if(YES == [[subStrings objectAtIndex:0x1] isEqualToString:@"-"])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//extract process id
|
|
// ->insides '()', so will be second substring
|
|
processID = @([[subStrings objectAtIndex:0x1] integerValue]);
|
|
if(nil == processID)
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//ignore self
|
|
if(cameraAssistant == processID.intValue)
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//get process path
|
|
// ->skip blank/unknown procs
|
|
processPath = getProcessPath(processID.intValue);
|
|
if( (nil == processPath) ||
|
|
(0 == processPath.length) )
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//ignore apple daemons (that send mach messages, etc)
|
|
if(YES == [ignoredProcs containsObject:processPath])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//add/inc to dictionary
|
|
senders[processID] = @([senders[processID] unsignedIntegerValue] + 1);
|
|
}
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return senders;
|
|
}
|
|
|
|
//invoke 'sample' to confirm candidates are using CMIO/video inputs
|
|
-(NSMutableArray*)sampleCandidates:(NSArray*)currentSenders
|
|
{
|
|
//current procs
|
|
NSMutableArray* videoProcs = nil;
|
|
|
|
//results from 'sample' cmd
|
|
NSString* results = nil;
|
|
|
|
//process path
|
|
NSString* processPath = nil;
|
|
|
|
//alloc
|
|
videoProcs = [NSMutableArray array];
|
|
|
|
//invoke 'sample' on each
|
|
// ->skips FaceTime.app though on macOS Sierra
|
|
for(NSNumber* processID in currentSenders)
|
|
{
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"processing %d for sampling", processID.intValue]);
|
|
|
|
//get process path
|
|
// ->skip ones that fail
|
|
processPath = getProcessPath(processID.intValue);
|
|
if( (nil == processPath) ||
|
|
(0 == processPath.length) )
|
|
{
|
|
//next
|
|
continue;
|
|
}
|
|
|
|
//if we're running on macOS Sierra and there is only 1 candidate proc and its FaceTime
|
|
// ->don't sample, as it does thing wierdly....
|
|
if( (YES == [processPath isEqualToString:FACE_TIME]) &&
|
|
([getOSVersion() [@"minorVersion"] intValue] >= 12) )
|
|
{
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, @"not sampling as candidate app is FaceTime on macOS Sierra");
|
|
|
|
//add
|
|
[videoProcs addObject:processID];
|
|
|
|
//next
|
|
continue;
|
|
|
|
}
|
|
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"sampling %d", processID.intValue]);
|
|
|
|
//exec 'sample' to get threads/dylibs
|
|
// ->uses 1.0 seconds for sampling time
|
|
results = [[NSString alloc] initWithData:execTask(SAMPLE, @[processID.stringValue, @"1"]) encoding:NSUTF8StringEncoding];
|
|
if( (nil == results) ||
|
|
(0 == results.length) )
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//sampling a process creates a temp file
|
|
//->delete it!
|
|
[self deleteSampleFile:processPath];
|
|
|
|
//for now, just check for 'CMIOGraph::DoWork'
|
|
// ->TODO: could look for dylibs, other calls, etc
|
|
if(YES != [results containsString:@"CMIOGraph::DoWork"])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//looks like a video proc!
|
|
[videoProcs addObject:processID];
|
|
}
|
|
|
|
return videoProcs;
|
|
}
|
|
|
|
//'sample' binary creates a file
|
|
// ->this looks for that file and deletes it
|
|
-(void)deleteSampleFile:(NSString*)processPath
|
|
{
|
|
//error
|
|
NSError* error = nil;
|
|
|
|
//files
|
|
NSArray* files = nil;
|
|
|
|
//grab all files in /tmp
|
|
files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/tmp/" error:&error];
|
|
if(nil != error)
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to enumerate files in /tmp, %@", error]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//find/delete file
|
|
for(NSString* file in files)
|
|
{
|
|
//skip non-sample files
|
|
if(YES != [file hasSuffix:@".sample.txt"])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//ignore files that don't contain process name
|
|
if(YES != [file containsString:[processPath lastPathComponent]])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"deleting sample file: %@", file]);
|
|
|
|
//delete
|
|
if(YES != [[NSFileManager defaultManager] removeItemAtPath:[@"/tmp" stringByAppendingPathComponent:file] error:&error])
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete %@ (%@)", file, error]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
}//all files
|
|
|
|
//bail
|
|
bail:
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
//set status of video
|
|
-(void)updateVideoStatus:(BOOL)isEnabled
|
|
{
|
|
//sync
|
|
@synchronized(self)
|
|
{
|
|
//set
|
|
self.videoActive = isEnabled;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
@end
|
|
|
|
|