Update AVMonitor.m

improved 10.15 support
This commit is contained in:
Patrick Wardle 2021-05-10 11:11:18 -04:00
parent f2490a083f
commit a738de0b0c
1 changed files with 200 additions and 105 deletions

View File

@ -280,7 +280,6 @@ extern os_log_t logHandle;
return; return;
} }
//TODO: log info mode ...more info?
//on Intel systems //on Intel systems
// monitor for video events via 'VDCAssistant' // monitor for video events via 'VDCAssistant'
-(void)monitorIntel -(void)monitorIntel
@ -512,7 +511,17 @@ extern os_log_t logHandle;
NSRegularExpression* regex = nil; NSRegularExpression* regex = nil;
//init regex //init regex
regex = [NSRegularExpression regularExpressionWithPattern:@"pid:(\\d*)," options:0 error:nil]; //macOS 10.16 (11)
if(@available(macOS 10.16, *))
{
//init
regex = [NSRegularExpression regularExpressionWithPattern:@"pid:(\\d*)," options:0 error:nil];
}
//macOS 10.15
else
{
regex = [NSRegularExpression regularExpressionWithPattern:@"0x([a-fA-F0-9]){20,}" options:0 error:nil];
}
//find built-in mic //find built-in mic
builtInMic = [self findBuiltInMic]; builtInMic = [self findBuiltInMic];
@ -535,78 +544,155 @@ extern os_log_t logHandle;
//inc //inc
msgCount++; msgCount++;
//tcc request //macOS 10.16 (11)
if(YES == [event.composedMessage containsString:@"function=TCCAccessRequest, service=kTCCServiceMicrophone"]) if(@available(macOS 10.16, *))
{ {
//client //tcc request
Client* client = nil; if(YES == [event.composedMessage containsString:@"function=TCCAccessRequest, service=kTCCServiceMicrophone"])
//pid
NSNumber* pid = nil;
//match
NSTextCheckingResult* match = nil;
//dbg msg
os_log_debug(logHandle, "new tcc access msg: %{public}@", event.composedMessage);
//match/extract pid
match = [regex firstMatchInString:event.composedMessage options:0 range:NSMakeRange(0, event.composedMessage.length)];
//no match?
if( (nil == match) ||
(NSNotFound == match.range.location) ||
(match.numberOfRanges < 2) )
{ {
//ignore //client
return; Client* client = nil;
//pid
NSNumber* pid = nil;
//match
NSTextCheckingResult* match = nil;
//dbg msg
os_log_debug(logHandle, "new tcc access msg: %{public}@", event.composedMessage);
//match/extract pid
match = [regex firstMatchInString:event.composedMessage options:0 range:NSMakeRange(0, event.composedMessage.length)];
//no match?
if( (nil == match) ||
(NSNotFound == match.range.location) ||
(match.numberOfRanges < 2) )
{
//ignore
return;
}
//extract pid
pid = @([[event.composedMessage substringWithRange:[match rangeAtIndex:1]] intValue]);
if(nil == pid)
{
//ignore
return;
}
//init client
client = [[Client alloc] init];
client.msgCount = msgCount;
client.pid = pid;
client.path = getProcessPath(pid.intValue);
client.name = getProcessName(client.path);
//dbg msg
os_log_debug(logHandle, "new client: %{public}@", client);
//add client
[self.audioClients addObject:client];
} }
//extract pid
pid = @([[event.composedMessage substringWithRange:[match rangeAtIndex:1]] intValue]);
if(nil == pid)
{
//ignore
return;
}
//init client
client = [[Client alloc] init];
client.msgCount = msgCount;
client.pid = pid;
client.path = getProcessPath(pid.intValue);
client.name = getProcessName(client.path);
//dbg msg
os_log_debug(logHandle, "new client: %{public}@", client);
//add client
[self.audioClients addObject:client];
} }
//macOS 10.15
//auth ok msg else
else if(YES == [event.composedMessage containsString:@"RECV: synchronous reply"]) {
//tcc request
if( (YES == [event.composedMessage containsString:@"TCCAccessRequest"]) &&
(YES == [event.composedMessage containsString:@"kTCCServiceMicrophone"]) )
{
//client
Client* client = nil;
//token
NSString* token = nil;
//pid substring
NSString* substring = nil;
//pid
unsigned int pid = 0;
//match
NSTextCheckingResult* match = nil;
//dbg msg
os_log_debug(logHandle, "new tcc access msg: %{public}@", event.composedMessage);
//match/extract pid
match = [regex firstMatchInString:event.composedMessage options:0 range:NSMakeRange(0, event.composedMessage.length)];
//no match?
if( (nil == match) ||
(NSNotFound == match.range.location) )
{
//ignore
return;
}
//extract token
token = [event.composedMessage substringWithRange:[match rangeAtIndex:0]];
if(token.length < 46) return;
//extract pid
substring = [token substringWithRange:NSMakeRange(42, 4)];
//convert to int
sscanf(substring.UTF8String, "%x", &pid);
if(0 == pid) return;
//init client
client = [[Client alloc] init];
client.msgCount = msgCount;
client.pid = @(htons(pid));
client.path = getProcessPath(client.pid.intValue);
client.name = getProcessName(client.path);
//dbg msg
os_log_debug(logHandle, "new client: %{public}@", client);
//add client
[self.audioClients addObject:client];
}
}
//tcc auth response
// check that a) auth ok b) msg is right after request
if(YES == [event.composedMessage containsString:@"synchronous reply"])
{ {
//client //client
__block Client* client = nil; __block Client* client = nil;
//(split) response //flag
NSArray* response = nil; BOOL isAuthorized = NO;
//dbg msg //dbg msg
os_log_debug(logHandle, "new client tccd response : %{public}@", event.composedMessage); os_log_debug(logHandle, "new client tccd response : %{public}@", event.composedMessage);
//response // look for:
response = [event.composedMessage componentsSeparatedByString:@"\n"]; //"result" => <bool: 0x1fcca5e70>: true
if( (response.count < 2) || for(NSString* response in [event.composedMessage componentsSeparatedByString:@"\n"])
(YES != [response[1] hasSuffix:@"2"]) ||
(YES != [response[1] containsString:@"auth_value"]) )
{ {
//ignore //no match?
return; if( (YES != [response hasSuffix:@"true"]) ||
} (YES != [response containsString:@"\"result\""]) )
{
continue;
}
//match
isAuthorized = YES;
//get last client //done
break;
}
//not auth'd?
if(YES != isAuthorized) return;
//auth, so get last client
// check that it the one in the *last* msg // check that it the one in the *last* msg
client = self.audioClients.lastObject; client = self.audioClients.lastObject;
if(client.msgCount != msgCount-1) if(client.msgCount != msgCount-1)
@ -656,42 +742,20 @@ extern os_log_t logHandle;
//flag //flag
BOOL cameraOn = NO; BOOL cameraOn = NO;
//selector for getting device id
SEL methodSelector = nil;
//device's connection id //device's connection id
unsigned int connectionID = 0; unsigned int connectionID = 0;
//dbg msg //dbg msg
os_log_debug(logHandle, "checking if any camera is active"); os_log_debug(logHandle, "checking if any camera is active");
//init selector
methodSelector = NSSelectorFromString(@"connectionID");
//are any cameras currently on? //are any cameras currently on?
for(AVCaptureDevice* currentCamera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) for(AVCaptureDevice* currentCamera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo])
{ {
//dbg msg //dbg msg
os_log_debug(logHandle, "device: %{public}@/%{public}@", currentCamera.manufacturer, currentCamera.localizedName); os_log_debug(logHandle, "device: %{public}@/%{public}@", currentCamera.manufacturer, currentCamera.localizedName);
//sanity check //get id
// make sure is has private 'connectionID' iVar connectionID = [self getAVObjectID:currentCamera];
if(YES != [currentCamera respondsToSelector:methodSelector])
{
//skip
continue;
}
//ignore leak warning
// we know what we're doing via this 'performSelector'
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//grab connection ID
connectionID = (unsigned int)[currentCamera performSelector:methodSelector withObject:nil];
//restore
#pragma clang diagnostic pop
//get state //get state
// is (any) camera on? // is (any) camera on?
@ -718,44 +782,72 @@ bail:
{ {
//mic //mic
AudioObjectID builtInMic = 0; AudioObjectID builtInMic = 0;
//selector for getting device id
SEL methodSelector = nil;
//init selector
methodSelector = NSSelectorFromString(@"connectionID");
//look for mic that belongs to apple //look for mic that belongs to apple
for(AVCaptureDevice* currentMic in [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]) for(AVCaptureDevice* currentMic in [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio])
{ {
//dbg msg //dbg msg
os_log_debug(logHandle, "device: %{public}@/%{public}@", currentMic.manufacturer, currentMic.localizedName); os_log_debug(logHandle, "device: %{public}@/%{public}@", currentMic.manufacturer, currentMic.localizedName);
//sanity check
if(YES != [currentMic respondsToSelector:methodSelector]) continue;
//check if apple //check if apple
// also check input source // also check input source
if( (YES == [currentMic.manufacturer isEqualToString:@"Apple Inc."]) && if( (YES == [currentMic.manufacturer isEqualToString:@"Apple Inc."]) &&
(YES == [[[currentMic activeInputSource] inputSourceID] isEqualToString:@"imic"]) ) (YES == [[[currentMic activeInputSource] inputSourceID] isEqualToString:@"imic"]) )
{ {
//ignore leak warning //grab ID
#pragma clang diagnostic push builtInMic = [self getAVObjectID:currentMic];
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//grab connection ID
builtInMic = (unsigned int)[currentMic performSelector:methodSelector withObject:nil];
//restore
#pragma clang diagnostic pop
//done //done
break; break;
} }
} }
return builtInMic; //not found?
// grab default
if(0 == builtInMic)
{
//get mic / id
builtInMic = [self getAVObjectID:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]];
//dbg msg
os_log_debug(logHandle, "Apple mic not found, defaulting to default (id: %d)", builtInMic);
}
return builtInMic;
}
//get av object's ID
-(UInt32)getAVObjectID:(AVCaptureDevice*)audioObject
{
//object id
AudioObjectID objectID = 0;
//selector for getting device id
SEL methodSelector = nil;
//init selector
methodSelector = NSSelectorFromString(@"connectionID");
//sanity check
if(YES != [audioObject respondsToSelector:methodSelector])
{
//bail
goto bail;
}
//ignore leak warning
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//grab connection ID
objectID = (unsigned int)[audioObject performSelector:methodSelector withObject:nil];
//restore
#pragma clang diagnostic pop
bail:
return objectID;
} }
//is built-in mic on? //is built-in mic on?
@ -864,6 +956,9 @@ bail:
//bail //bail
goto bail; goto bail;
} }
//dbg msg
os_log_debug(logHandle, "monitoring %d for audio changes", builtInMic);
//happy //happy
bRegistered = YES; bRegistered = YES;