support (beta) for mic's process ID

This commit is contained in:
patrick 2016-11-06 17:33:42 -10:00
parent c798aa6912
commit 65dbbf9a9a
7 changed files with 610 additions and 31 deletions

View File

@ -206,7 +206,7 @@
[self setVideoDevStatus:connectionID];
//if video is already active
// ->start monitoring thread
// ->set status & start monitoring thread
if(YES == self.videoActive)
{
//dbg msg
@ -273,7 +273,32 @@
//restore
#pragma clang diagnostic pop
//save camera/status into device array
//set status
// ->will set 'videoActive' iVar
[self setAudioDevStatus:connectionID];
//if audio is already active
// ->tell XPC that it's active
// TODO: monitor for hijacking?
if(YES == self.audioActive)
{
//dbg msg
logMsg(LOG_DEBUG, @"audio already active");//so will start polling for new video procs");
//tell XPC audio is active
[[xpcConnection remoteObjectProxy] updateAudioStatus:self.audioActive reply:^{
//signal sema
dispatch_semaphore_signal(waitSema);
}];
//wait until XPC is done
// ->XPC reply block will signal semaphore
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
}
//save mic/status into device array
[devices addObject:@{EVENT_DEVICE:self.mic, EVENT_DEVICE_STATUS:@(self.audioActive)}];
//register for audio events
@ -649,6 +674,12 @@ bail:
//event dictionary
NSMutableDictionary* event = nil;
//xpc connection
__block NSXPCConnection* xpcConnection = nil;
//wait semaphore
dispatch_semaphore_t waitSema = nil;
//init dictionary
event = [NSMutableDictionary dictionary];
@ -657,6 +688,7 @@ bail:
{
//set status
// ->updates 'audioActive' iVar
[self setAudioDevStatus:deviceID];
//send msg to status menu
@ -671,12 +703,102 @@ bail:
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got audio change notification; is running? %x", self.audioActive]);
//generate notification
[self generateNotification:event];
//alloc XPC connection
xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"];
//set remote object interface
xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
//resume
[xpcConnection resume];
//init wait semaphore
waitSema = dispatch_semaphore_create(0);
//tell XPC about audio status
// ->for example, when audio is active, will stop baselining
[[xpcConnection remoteObjectProxy] updateAudioStatus:self.audioActive reply:^{
//signal sema
dispatch_semaphore_signal(waitSema);
}];
//wait until XPC is done
// ->XPC reply block will signal semaphore
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
//if video just started
// ->ask for video procs from XPC
if(YES == self.audioActive)
{
//dbg msg
logMsg(LOG_DEBUG, @"audio is active, so querying XPC to get audio process(s)");
//set allowed classes
[xpcConnection.remoteObjectInterface setClasses: [NSSet setWithObjects: [NSMutableArray class], [NSNumber class], nil]
forSelector: @selector(getAudioProcs:) argumentIndex: 0 ofReply: YES];
//invoke XPC service
[[xpcConnection remoteObjectProxy] getAudioProcs:^(NSMutableArray* audioProcesses)
{
//close connection
[xpcConnection invalidate];
//nil out
xpcConnection = nil;
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"audio procs from XPC: %@", audioProcesses]);
//generate notification for each process
for(NSNumber* processID in audioProcesses)
{
//set pid
event[EVENT_PROCESS_ID] = processID;
//generate notification
[self generateNotification:event];
}
//if no consumer process was found
// ->still alert user that webcam was activated, but without details/ability to block
if(0 == audioProcesses.count)
{
//set pid
event[EVENT_PROCESS_ID] = @0;
//generate notification
[self generateNotification:event];
}
//signal sema
dispatch_semaphore_signal(waitSema);
}];
//wait until XPC is done
// ->XPC reply block will signal semaphore
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
}
//audio deactivated
// ->close XPC connection and alert user
else
{
//close connection
[xpcConnection invalidate];
//nil out
xpcConnection = nil;
//generate notification
[self generateNotification:event];
}
}//sync
//bail
bail:
@ -801,9 +923,8 @@ bail:
details = ((AVCaptureDevice*)event[EVENT_DEVICE]).localizedName;
//customize buttons
// ->for mic, inactive events, or when consumer proc couldn't be ID'd, just say 'ok'
if( (YES == [event[EVENT_DEVICE] isKindOfClass:NSClassFromString(@"AVCaptureHALDevice")]) ||
(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) ||
// ->inactive events, or when consumer proc couldn't be ID'd, just say 'ok'
if( (YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) ||
(0 == [event[EVENT_PROCESS_ID] intValue]) )
{
//set other button title
@ -814,7 +935,7 @@ bail:
}
//customize buttons
// ->for activated video; allow/block
// ->for activated audio/video; allow/block
else
{
//get process name

View File

@ -18,6 +18,9 @@
//camera assistant daemon
#define APPLE_CAMERA_ASSISTANT @"/Library/CoreMediaIO/Plug-Ins/DAL/AppleCamera.plugin/Contents/Resources/AppleCameraAssistant"
//core audio
#define CORE_AUDIO @"/usr/sbin/coreaudiod"
//lsmp binary
#define LSMP @"/usr/bin/lsmp"
@ -27,11 +30,22 @@
/* PROPERTIES */
//flag indicating video is action
//flag indicating video is active
@property BOOL videoActive;
//flag indicating mic is active
@property BOOL audioActive;
//list of procs that have send Mach msg to *Assistant
@property(nonatomic, retain)NSMutableDictionary* machSenders;
@property(nonatomic, retain)NSMutableDictionary* machSendersVideo;
//list of procs that have send Mach msg to coreaudio
@property(nonatomic, retain)NSMutableDictionary* machSendersAudio;
//list of procs that have i/o reg entries
// ->IOService:/AppleACPIPlatformExpert/IOPMrootDomain/RootDomainUserClient
@property(nonatomic, retain)NSMutableDictionary* userClients;
/* METHODS */
@ -46,10 +60,17 @@
//find 'VDCAssistant' or 'AppleCameraAssistant'
-(pid_t)findCameraAssistant;
//enumerate all (recent) process that appear to be using the mic
-(NSMutableArray*)enumAudioProcs;
//enumerate all (recent) process that appear to be using video
-(NSMutableArray*)enumVideoProcs;
//set status of audio
-(void)updateAudioStatus:(BOOL)isEnabled;
//set status of video
-(void)updateVideoStatus:(BOOL)isEnabled;
@end

View File

@ -21,8 +21,11 @@ static NSArray* ignoredProcs = nil;
@implementation Enumerator
@synthesize machSenders;
@synthesize audioActive;
@synthesize userClients;
@synthesize videoActive;
@synthesize machSendersAudio;
@synthesize machSendersVideo;
//init
-(instancetype)init
@ -30,9 +33,6 @@ static NSArray* ignoredProcs = nil;
//init
if(self = [super init])
{
//alloc dictionary
machSenders = [NSMutableDictionary dictionary];
//init ignored procs
ignoredProcs = @[
@"/sbin/launchd",
@ -58,7 +58,7 @@ static NSArray* ignoredProcs = nil;
+(id)sharedManager
{
//instance
static Enumerator *sharedEnumerator = nil;
static Enumerator* sharedEnumerator = nil;
//once token
static dispatch_once_t onceToken;
@ -75,6 +75,84 @@ static NSArray* ignoredProcs = nil;
return sharedEnumerator;
}
//find a process by name
-(pid_t)findProcess:(NSString*)processName
{
//pid
pid_t processID = 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;
}
//match?
if(YES == [processPath isEqualToString:processName])
{
//save
processID = pids[i];
//pau
break;
}
}
//bail
bail:
//free buffer
if(NULL != pids)
{
//free
free(pids);
}
return processID;
}
//TODO: replace with [self findProcess]!!
//find 'VDCAssistant' or 'AppleCameraAssistant'
-(pid_t)findCameraAssistant
{
@ -161,12 +239,12 @@ bail:
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
//forever, baseline by getting all current procs that have sent a mach msg to *Assistant / coreaudio
// ->logic only exec'd while camera/mic is not in use, so these are all just baselined procs
-(void)start
{
//baseline forever
// ->though logic will skip if video is active
// ->though logic will skip if video or mic is active (respectively)
while(YES)
{
//sync baselining
@ -176,13 +254,35 @@ bail:
if(YES != self.videoActive)
{
//dbg msg
logMsg(LOG_DEBUG, @"baselining mach senders...");
logMsg(LOG_DEBUG, @"baselining mach senders for video...");
//enumerate procs that have send mach messages
self.machSenders = [self enumMachSenders:[self findCameraAssistant]];
self.machSendersVideo = [self enumMachSenders:[self findCameraAssistant]];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu baselined mach senders: %@", (unsigned long)self.machSenders.count, self.machSenders]);
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu baselined mach senders: %@", (unsigned long)self.machSendersVideo.count, self.machSendersVideo]);
}
//only baseline if video isn't active
if(YES != self.audioActive)
{
//dbg msg
logMsg(LOG_DEBUG, @"baselining mach senders for audio...");
//enumerate procs that have send mach messages
self.machSendersAudio = [self enumMachSenders:[self findProcess:CORE_AUDIO]];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu baselined mach senders: %@", (unsigned long)self.machSendersAudio.count, self.machSendersVideo]);
//dbg msg
logMsg(LOG_DEBUG, @"baselining i/o registry entries for audio...");
//enumerate procs that have i/o registry entries
self.userClients = [self enumDomainUserClients];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu baselined i/or registry senders: %@", (unsigned long)self.userClients.count, self.userClients]);
}
}
@ -240,7 +340,7 @@ bail:
{
//add any candidate procs
// ->those that have new mach message
if( [currentSenders[processID] intValue] > [self.machSenders[processID] intValue])
if( [currentSenders[processID] intValue] > [self.machSendersVideo[processID] intValue])
{
//ignore client/requestor
if(clientPID == processID.intValue)
@ -258,7 +358,7 @@ bail:
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu candidate video procs: %@", (unsigned long)candidateVideoProcs.count, candidateVideoProcs]);
//update
self.machSenders = currentSenders;
self.machSendersVideo = 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
@ -272,9 +372,175 @@ bail:
return videoProcs;
}
//get procs that currrently have sent Mach msg to *Assistant
//enumerate all (recent) process that appear to be using the mic
-(NSMutableArray*)enumAudioProcs
{
//current procs
NSMutableArray* audioProcs = nil;
//current mach senders
NSMutableDictionary* currentSenders = nil;
//new senders
NSMutableArray* newSenders = nil;
//current domain user clients (from i/o registry)
NSMutableDictionary* currentUserClients = nil;
//new user clients
NSMutableArray* newUserClients = nil;
//candidate audio procs
// ->those that have new mach message
NSMutableArray* candidateAudioProcs = nil;
//itersection set
NSMutableSet* intersection = nil;
//pid of coreaudio process
pid_t coreAudio = 0;
//alloc array
newSenders = [NSMutableArray array];
//alloc array
newUserClients = [NSMutableArray array];
//alloc array
candidateAudioProcs = [NSMutableArray array];
//sync this logic
// ->prevent baselining thread from doing anything
@synchronized(self)
{
//find coreaudio
coreAudio = [self findProcess:CORE_AUDIO];
if(0 == coreAudio)
{
//err msg
logMsg(LOG_ERR, @"failed to find coreaudio process");
//bail
goto bail;
}
//get procs that currrently have sent Mach msg to core audio
// ->returns dictionary of process id, and number of mach messages
currentSenders = [self enumMachSenders:coreAudio];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu current mach senders: %@", (unsigned long)currentSenders.count, currentSenders]);
//add new senders or those w/ new mach msgs
for(NSNumber* processID in currentSenders.allKeys)
{
//ignore client/requestor (self)
if(clientPID == processID.intValue)
{
//skip
continue;
}
//skip any that don't have new mach message
if( (nil != self.machSendersAudio[processID]) &&
([self.machSendersAudio[processID] intValue] >= [currentSenders[processID] intValue]) )
{
//skip
continue;
}
//ok new, so add
[newSenders addObject:processID];
}
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"new mach senders: %@", newSenders]);
//update iVar
self.machSendersAudio = currentSenders;
//grab current 'IOPMrootDomain/RootDomainUserClient/IOUserClientCreator's
currentUserClients = [self enumDomainUserClients];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu current i/o registry user clients: %@", (unsigned long)currentUserClients.count, currentUserClients]);
//add new user clients
for(NSNumber* processID in currentUserClients.allKeys)
{
//ignore client/requestor (self)
if(clientPID == processID.intValue)
{
//skip
continue;
}
//skip any that don't have new mach message
if( (nil != self.userClients[processID]) &&
([self.userClients[processID] intValue] >= [currentUserClients[processID] intValue]) )
{
//skip
continue;
}
//ok new, so add
[newUserClients addObject:processID];
}
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"new user clients: %@", newUserClients]);
//update iVar
self.userClients = currentUserClients;
AudioUnitRender
/*
//parse/check
// ->starts at end to find most recent IOUserClientCreator
for(NSNumber* domainUserClient in [domainUserClients reverseObjectEnumerator])
{
//no match?
// ->remove from candidate
if(YES != [candidateAudioProcs containsObject:domainUserClient])
{
//remove
}
}
*/
//init set for intersection
intersection = [NSMutableSet setWithArray:newSenders];
//get procs that have sent mach messages *and* have an entry in the i/o registry
[intersection intersectSet:[NSMutableSet setWithArray:newUserClients]];
//assign
candidateAudioProcs = [[intersection allObjects] mutableCopy];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu candidate audio procs: %@", (unsigned long)candidateAudioProcs.count, candidateAudioProcs]);
//TODO: sample?
//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
//audioProcs = [self sampleCandidates:candidateAudioProcs];
audioProcs = candidateAudioProcs;
}//sync
//bail
bail:
return audioProcs;
}
//get procs that currrently have sent Mach msg to a target process
// ->returns dictionary of process id, and number of mach messages
-(NSMutableDictionary*)enumMachSenders:(pid_t)cameraAssistant
-(NSMutableDictionary*)enumMachSenders:(pid_t)targetProcess
{
//senders
NSMutableDictionary* senders = nil;
@ -295,7 +561,7 @@ bail:
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];
results = [[NSString alloc] initWithData:execTask(LSMP, @[@"-p", @(targetProcess).stringValue]) encoding:NSUTF8StringEncoding];
if( (nil == results) ||
(0 == results.length) )
{
@ -339,8 +605,8 @@ bail:
continue;
}
//ignore self
if(cameraAssistant == processID.intValue)
//ignore target process
if(targetProcess == processID.intValue)
{
//skip
continue;
@ -373,6 +639,108 @@ bail:
return senders;
}
//iterate thru i/o registry to get all RootDomainUserClient under IOPMrootDomain
// ->returns dictionary of process id, and number of user client entries
-(NSMutableDictionary*)enumDomainUserClients
{
//matching service
io_service_t matchingService = 0;
//iterator
io_iterator_t iterator = 0;
//kids
io_registry_entry_t child = 0;
//array of RootDomainUserClients
NSMutableDictionary* clients = nil;
//client creator
CFTypeRef creator = 0;
//for parsing
NSArray* components = nil;
//process id
NSNumber* processID = nil;
//alloc
clients = [NSMutableDictionary dictionary];
//get IOPMrootDomain obj
matchingService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMrootDomain"));
if(0 == matchingService)
{
//bail
goto bail;
}
//get iterator
if(noErr != IORegistryEntryGetChildIterator(matchingService, kIOServicePlane, &iterator))
{
//bail
goto bail;
}
//iterator over all children
// ->store all that have 'IOUserClientCreator'
while((child = IOIteratorNext(iterator)))
{
//try get creator
creator = IORegistryEntryCreateCFProperty(child, CFSTR("IOUserClientCreator"), kCFAllocatorDefault, 0);
//always release child
IOObjectRelease(child);
//if couldn't get a creator
// ->might just not be of RootDomainUserClient, so skip
if(0 == creator)
{
//skip
continue;
}
//parse
components = [(__bridge NSString*)creator componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" ,"]];
//extact pid and save
if(components.count >= 4)
{
//grab pid
// format is: "pid 4781, process"
processID = [NSNumber numberWithShort:[components[0x1] intValue]];
if(0 != processID.intValue)
{
//add/inc to dictionary
clients[processID] = @([clients[processID] unsignedIntegerValue] + 1);
}
}
//release
CFRelease(creator);
}
//bail
bail:
//release iterator
if(0 != iterator)
{
//release
IOObjectRelease(iterator);
}
//release obj
if(0 != matchingService)
{
//release
IOObjectRelease(matchingService);
}
return clients;
}
//invoke 'sample' to confirm candidates are using CMIO/video inputs
-(NSMutableArray*)sampleCandidates:(NSArray*)currentSenders
{
@ -514,6 +882,7 @@ bail:
}
//set status of video
// ->extra logic is executed to 'refresh' iVars when video is disabled
-(void)updateVideoStatus:(BOOL)isEnabled
{
//sync
@ -521,6 +890,39 @@ bail:
{
//set
self.videoActive = isEnabled;
//when video disabled
// ->re-enumerate mach senders
if(YES != isEnabled)
{
//enumerate mach senders
self.machSendersVideo = [self enumMachSenders:[self findCameraAssistant]];
}
}
return;
}
//set status of audio
// ->extra logic is executed to 'refresh' iVars when audio is disabled
-(void)updateAudioStatus:(BOOL)isEnabled
{
//sync
@synchronized(self)
{
//set
self.audioActive = isEnabled;
//when audio disabled
// ->re-enumerate mach senders & i/o registry user clients
if(YES != isEnabled)
{
//enumerate mach senders
self.machSendersAudio = [self enumMachSenders:[self findProcess:CORE_AUDIO]];
//enumerate i/o registry user clients
self.userClients = [self enumDomainUserClients];
}
}
return;

View File

@ -40,6 +40,15 @@
return;
}
//call into emumerate to get (new) audio proc
-(void)getAudioProcs:(void (^)(NSMutableArray *))reply
{
//reply w/ video procs
reply([[Enumerator sharedManager] enumAudioProcs]);
return;
}
//update status video
// ->allows enumerator to stop baselining (when active), etc
-(void)updateVideoStatus:(unsigned int)status reply:(void (^)(void))reply
@ -53,6 +62,19 @@
return;
}
//update status audio
// ->allows enumerator to stop baselining (when active), etc
-(void)updateAudioStatus:(unsigned int)status reply:(void (^)(void))reply
{
//set status
[[Enumerator sharedManager] updateAudioStatus:status];
//reply
reply();
return;
}
//kill a process
-(void)killProcess:(NSNumber*)processID reply:(void (^)(BOOL))reply
{

View File

@ -39,6 +39,11 @@ pid_t clientPID = 0;
//signing req string
NSString *requirementString = nil;
//TODO: re-enable
/*
//init signing req string
requirementString = [NSString stringWithFormat:@"anchor trusted and certificate leaf [subject.CN] = \"%@\"", SIGNING_AUTH];
@ -59,6 +64,8 @@ pid_t clientPID = 0;
goto bail;
}
*/
//set the interface that the exported object implements
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
@ -130,4 +137,4 @@ int main(int argc, const char *argv[])
bail:
return status;
}
}

View File

@ -90,5 +90,4 @@
#define FACE_TIME @"/Applications/FaceTime.app/Contents/MacOS/FaceTime"
#endif

View File

@ -14,6 +14,9 @@
//start enumerator
-(void)initialize:(void (^)(void))reply;
//get (new) audio procs
-(void)getAudioProcs:(void (^)(NSMutableArray *))reply;
//get (new) video procs
-(void)getVideoProcs:(void (^)(NSMutableArray *))reply;
@ -21,6 +24,10 @@
// ->allows enumerator to stop baselining (when active), etc
-(void)updateVideoStatus:(unsigned int)status reply:(void (^)(void))reply;
//update status video
// ->allows enumerator to stop baselining (when active), etc
-(void)updateAudioStatus:(unsigned int)status reply:(void (^)(void))reply;
//kill a process
-(void)killProcess:(NSNumber*)processID reply:(void (^)(BOOL))reply;