How to spy on your iOS users - a missing camera privacy feature
Tags: jailbreak, ios, privacy, permission
Published: 2020-05-15 18:50:03

## History

I think everyone remembers the scandal (around November 2019) about an app in which the camera was activated in the "background" due to a bug.
If you missed it, quickly Google it. The online media was full of these kinds of articles at that time.

Then my mind did not let me sleep...

## Camera Permission on iOS

[Description](https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/requesting_authorization_for_media_capture_on_ios?language=objc) from Apple:


>In iOS, the user must explicitly grant permission for each app to access cameras and microphones. Before your app can use the capture system for the first time, iOS shows an alert asking the user to grant your app access to the camera, as shown below. **iOS remembers the user’s response to this alert, so subsequent uses of the capture system don’t cause it to appear again.** The user can change permission settings for your app in Settings > Privacy.

## Camera API

[The Camera API usage](https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/setting_up_a_capture_session) is pretty straightforward. You define which camera hardware you would like to use, how to show a preview, and where to save the files.

## The Problem

As you see from the documentation above, once the user grants the Camera Permission for an app, **it is granted. Forever.**

Anyone can make an application that asks for Camera Permission. e.g. taking a photo, uploading an image, setting profile picture, "scanning" document, etc. You can find many use-cases that can justify camera usage.
The application can ask it for a valid reason, but after that, you don't know what can happen.

And do you know if the app is still using your camera while you are in the app? Probably not.

Let your imagination fly a bit and think about what you could do if you could have access to the user's camera anytime in the app.
>You have a home decor online shop. While the user is searching for a certain item in your app you could look around in his/her house, see the colors, furniture, and your app could start suggesting extra items in matching style.

Or:
>Your main revenue stream is displaying ads in your app. What if you could monitor the user's reaction to an ad? Then you could offer to your advertiser more insights about what kind of ads a certain type of user likes.

This information would be pretty valuable, right? Ethical? I don't think so. 

## Proof of Concept
### The Good

**Note:** If you would like to go straight to the code, here you are:  [HiddenCamera](https://github.com/Danesz/PrivacyGuard-iOS/tree/master/HiddenCam) app on GitHub.

As a first step, you have to define the [NSCameraUsageDescription](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/plist/info/NSCameraUsageDescription) key Info.plist file of your application. You have to define a purpose string for this key why your app needs this permission. Obviously, taking pictures... right?

![Info.plist file](https://dev-to-uploads.s3.amazonaws.com/i/g0x2u3i512ad0o686ypg.png)

This description will be part of the alert message that will be shown to the users the first time when they use your app.

![Asking permission popup](https://dev-to-uploads.s3.amazonaws.com/i/vzw58p6bhr9dham1yw1t.png)

So, let's take a look at our [CameraViewController](https://github.com/Danesz/PrivacyGuard-iOS/blob/master/HiddenCam/HiddenCam/CamerVIewController.swift).

We need to initialize an `AVCaptureSession`. 
``` swift
let session = AVCaptureSession()
```
This session is the base of everything. Here we can attach our input/output elements.
``` swift
var deviceInput: AVCaptureDeviceInput!

session.sessionPreset = AVCaptureSession.Preset.vga640x480

// acquire the camera device
guard let device = AVCaptureDevice
    .default(AVCaptureDevice.DeviceType.builtInWideAngleCamera,
             for: .video,
             position: AVCaptureDevice.Position.front) else {
                return
}

do {
    deviceInput = try AVCaptureDeviceInput(device: device)
    guard deviceInput != nil else {
        print("error: can't get deviceInput")
        return
    }
    
    // add the Input device to the session
    if self.session.canAddInput(deviceInput){
        self.session.addInput(deviceInput)
    }
    
    // define a Video output device to capture video frames from the camera
    videoDataOutput = AVCaptureVideoDataOutput()
    videoDataOutput.alwaysDiscardsLateVideoFrames = true
    videoDataOutputQueue = DispatchQueue(label: "VideoDataOutputQueue")
    videoDataOutput.setSampleBufferDelegate(self, queue:self.videoDataOutputQueue)
    
    if session.canAddOutput(self.videoDataOutput){
        session.addOutput(self.videoDataOutput)
    }
    
    //define a Picture output device to capture still images from the camera
    stillImageOutput = AVCapturePhotoOutput()
    
    if session.canAddOutput(stillImageOutput) {
        session.addOutput(self.stillImageOutput)
    }
    
    videoDataOutput.connection(with: .video)?.isEnabled = true
    
    // define the camera preview view
    previewLayer = AVCaptureVideoPreviewLayer(session: self.session)
    previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect
    
  	// ...
    
    // and start running the capture session
    session.startRunning()
} catch let error as NSError {
    deviceInput = nil
    print("error: \(error.localizedDescription)")
}
```
The `startRunning()` method kicks in the whole capturing process. (This will be important later!)

Now we can listen to the video delegate callbacks:
``` swift
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    // do stuff here with the video
}
```
Then to the photo delegate callbacks:
``` swift
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    //process single image
    guard let imageData = photo.fileDataRepresentation()
        else { return }
    
    let captureImageView = UIImageView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
    let image = UIImage(data: imageData)
    captureImageView.image = image
    
    //show the captured image
    let centerView = UIView(frame: CGRect(x: UIScreen.main.bounds.size.width / 2 - 100,
                                          y: UIScreen.main.bounds.size.height / 2 - 100,
                                          width: 200,
                                          height: 200))
    centerView.backgroundColor = UIColor.red
    centerView.addSubview(captureImageView)
    
    self.view.addSubview(centerView)
    
}
```
As you can see, nothing evil.
We ask for permission, the user grants it, we show the video preview and the captured photo.

Let's see how it works:

{% youtube -BI70Twu_QI %}

### The Bad

We can take a look on the [CameraViewControllerHidden](https://github.com/Danesz/PrivacyGuard-iOS/blob/master/HiddenCam/HiddenCam/CamerVIewControllerHidden.swift) class.
Same thing as above, but unfortunately by mistake we forgot to attach our video preview to the view.

``` swift
// ...
do {
    deviceInput = try AVCaptureDeviceInput(device: captureDevice)
    guard deviceInput != nil else {
        print("error: can't get deviceInput")
        return
    }
    
    if self.session.canAddInput(deviceInput){
        self.session.addInput(deviceInput)
    }
    
    stillImageOutput = AVCapturePhotoOutput()
    
    if session.canAddOutput(stillImageOutput) {
        session.addOutput(self.stillImageOutput)
    }
    
    // we can also attach videoDataOutput from the previous example to get all the video frames
    // but for now image capturing is enough

    // and whoopsie, we forgot the preview :(
    
    session.startRunning()
}
// ...
```
What do we see now?

No permission was asked (previously it was granted forever), no video preview.
So we think everything is alright, we are browsing our feed, watching ads, liking/disliking photos... meanwhile someone is spying on us: 

{% youtube noPKjEO8MDI %}

Just to give you a little peace of mind: at least the camera stream is blocked while the app is in the background.

## Jailbreak to the Rescue

What is [Jailbreaking](https://en.wikipedia.org/wiki/IOS_jailbreaking)?
>Jailbreaking is the privilege escalation of an Apple device for the purpose of removing software restrictions imposed by Apple on iOS, iPadOS, tvOS and watchOS operating systems. This is typically done by using a series of kernel patches. Jailbreaking permits root access in Apple's mobile operating system, allowing the installation of software that is unavailable through the official Apple App Store. Many types of jailbreaking are available, for different versions.

[Apple publicly disapproves of jailbreaking.](https://support.apple.com/en-gb/HT201954)

There are a lot of pros and cons of jailbreaking. You can find some of these on the above-mentioned links.

Jailbreaking is for users who know what they are doing. And these people like to jailbreak for tweaking their devices.
Just take a look at [all the magic](https://www.idownloadblog.com/tag/jailbreak-apps-tweaks/) you can do on your iOS device once it is jailbroken.

### The Tweak

The [tweaks](https://en.wikipedia.org/wiki/IOS_jailbreaking#Device_customization) are iOS applications, however
>...many of them are not typical self-contained apps but instead are extensions and customization options for iOS and its features and other apps (commonly called tweaks). Users install these programs for purposes including personalization and customization of the interface by tweaks developed by developers and designers, adding desired features and fixing annoyances...

### PrivacyGuard - iOS

**Note: This post is not elaborating how to develop a Jailbreak tweak. That's for a future post. If you want to hear more about it, please subscribe to the [content notifications](https://subscribe.danieldallos.com).**

As you read above, most of the Jailbreak tweaks are extending the functionality of already existing apps. How? In a nutshell, they hook into existing methods in the app and react when the method is called. They can completely override it, alter its behavior, or just trigger a jailbreak code and call the original method after it.

This is exactly what we will do.

**Note:** If you would like to go straight to the code, here you are:  [PrivacyGuard-iOS](https://github.com/Danesz/PrivacyGuard-iOS/tree/master/privacyguard) tweak on GitHub.

As we saw above in the camera app examples, everything starts with the `startRunning()` method on `AVCaptureSession`.

It's time to [hook](https://github.com/Danesz/PrivacyGuard-iOS/blob/master/privacyguard/Tweak.x)!

``` objectivec
#import <UIKit/UIKit.h>

%hook SpringBoard
-(void) applicationDidFinishLaunching:(id)arg {
	%orig(arg);
	UIAlertView *lookWhatWorks = [[UIAlertView alloc] initWithTitle:@"PrivacyGuard Tweak"
		message:@"Your privacy guard is running 😎"
		delegate:self
		cancelButtonTitle:@"OK"
		otherButtonTitles:nil];
	[lookWhatWorks show];
}
%end

%hook AVCaptureSession

// Hooking an instance method with no arguments.
-(void) startRunning {
	NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
	NSLog(@"PrivacyGuard startRunning: %@", appName);

	UIView *statusBar = [[UIApplication sharedApplication] valueForKey:@"statusBar"];
	statusBar.backgroundColor = [UIColor greenColor];

	%orig;

}

-(void) stopRunning {
	NSLog(@"PrivacyGuard stopRunning");

	UIView *statusBar = [[UIApplication sharedApplication] valueForKey:@"statusBar"];
	statusBar.backgroundColor = [UIColor clearColor]; // or we could save and restore the original one

	%orig;
}

// Always make sure you clean up after yourself; Not doing so could have grave consequences!
%end
```

Easy (for now) and beautiful!

(The first part, hooking into `Springboard`, just a helper notification to show the tweak is initialized.)

We hook directly into `AVCaptureSession`'s `startRunning()` method. Whenever this method is called we log to the console the current app that is using the camera (initiated the capturing) and change the status bar color to green to visually notify ourselves.
After this is done we just call the original (`%orig`) method and let the flow continue.
Hooking into the `stopRunning()` method is useful to revert our changes after the camera is not in use anymore.

How does it look while using the example apps above?

When you have the preview visible:
{% youtube VjPIccppKzI %}

And when we are in evil mode:
{% youtube QdRo74cjcPU %}

Also, in the logs we can see the relevant app info:
![PrivacyGuard log](https://dev-to-uploads.s3.amazonaws.com/i/xfe2gsjkt6qmdv70xwzx.png)

And no hidden surprises anymore.
You can go further. Showing a popup, notification, or a little icon in the status bar while the camera is in use. It is all up to you!

**Note:** PrivacyGuard-iOS is not available in any Jailbreak repository (yet). If you have the urge to try it, please let me know. But also feel free to compile it and tweak it for yourself.

**Note:** If there are other ways of accessing the camera stream those were not part of this investigation. Probably there you can use the same approach.

## Conclusions

### Jailbreak Tweaks

The tweaks made by the Jailbreak community are amazing. A lot of smart ideas, solutions for daily annoyances. 

Having the possibility to use a jailbroken device and develop your own tweaks for your development work can be really interesting.
It opens your possibilities, you have more access to the system, you can debug more things, private APIs.

It is brilliant.

### Camera API

I think you can draw your own conclusion.

Be aware of your apps and used services.
Maybe you don't even need those apps, just try to use the web version of those services. Check the granted permissions of your apps, maybe for your use-case it does not need extra permission.

Sensitive permissions should not be granted forever.
Hopefully, Apple will fix this issue soon like Google is doing it on [Android 11](https://developer.android.com/preview/privacy/permissions) now.

## Disclaimer
I don't say that any of these things are happening in any apps on the market and I don't encourage you to jailbreak your iOS device.

But better to be prepared and be more privacy-conscious. Having these kinds of data collected about a user/content/ad could be valuable for certain parties.

## What’s next?
We will look around on Android.
[Stay tuned and register for content notifications!](https://subscribe.danieldallos.com)