Welcome to the first post of our latest blog series:
Mobile Attacker’s Mindset
In this blog series, we’re going to cover how mobile threat-actors think, and what techniques attackers use to overcome security protections and indications that our phones and tablets are compromised.
In this first blog, we’ll demonstrate how the recently added camera & microphone green/orange indicators do not pose a real security challenge to mobile threat actors.
Starting iOS 14, there are a couple of new indicators: a green dot, and an orange dot. These indicators signal when the camera or the microphone are accessed.
We are less worried about the phone listening to us, when there’s no green/orange dot.
We know that malware like NSO/Pegasus is capable of listening to the microphone. Can NSO Group and hundreds of other threat actors that target mobile devices can take videos of us while the visual indicator is off?
Let’s examine if this feature poses any challenge to attackers.
Let’s first think about it. Is the indicator really on everytime the camera or microphone are accessed? We quickly think about Siri. How does the phone know when we say “Hey Siri” if the microphone indicator is not on all the time? The phone must be listening somehow right.
/System/Library/PrivateFrameworks/CoreSpeech.framework/corespeechd relies on VoiceTrigger.framework to continuously monitor the user’s voice, and then activate Siri when a keyword is heard.
Accessibility -> VoiceControl
Voice control allows you to interact with the device using voice commands.
is responsible for accessing the microphone.
Accessibility -> Switch Control
Part of the SwitchControl function is to detect the movement of the user’s head to interact with the device. Very cool feature! It’s handled by:
These features must access the microphone or camera to function. However, these features do not trigger the green/orange visual indicators. This means that mobile malware can do the same.
This means that by injecting a malicious thread into com.apple.accessibility.AccessibilityUIServer / com.apple.SpeechRecognitionCore.speechrecognitiond daemons attackers can enable silent access to the microphone. Camera access requires additional patch which we will talk about it later
Bypass TCC Prompt
TCC stands for “Transparency, Consent, and Control”. iOS users often experience this prompt:
The core of TCC is a system daemon called tccd, and it manages access to sensitive databases and the permission to collect sensitive data from input devices, including but not limited to microphone and camera.
Did you know? TCC prompt only applies to applications with UI interface. Anything running in the background requires special entitlement to operate. The entitlement looks like the picture below. Just kTCCServiceMicrophone is enough for microphone access.
Camera access is a little more complicated. In addition to tccd, there is another system daemon called mediaserverd ensures that no process with background running state can access the camera.
So far, it looks like an extra step (e.g. patching mediaserverd) is needed to access the camera in the background while the user is interacting with another foreground application.
Disable Visual Indicators For Microphone, Camera Access
First method is rough, using Cycript to inject code into SpringBoard, causing the indicator to disappear abruptly.
Inspired by com.apple.SpeechRecognitionCore.speechrecognitiond and com.apple.accessibility.AccessibilityUIServer, a private entitlement (com.apple.private.mediaexperience.suppressrecordingstatetosystemstatus) that fits perfectly for our purpose! Unfortunately, this method does not work for camera access.
Accessing Camera in the Background by Patching ‘mediaserverd’
mediaserverd is a daemon that monitors media capture sessions. Processes that want to access the camera must be approved by tccd as well as mediaserverd. It is an extra layer of security after tccd. It also terminates the camera access when it detects an application is no longer running in the foreground.
Noteworthy, mediaserverd is equipped with a special entitlement (get-task-allow) to prevent code injection.
As a result of “get-task-allow” entitlement, dynamic debugger relies on obtaining task ports like cycript, frida do not work on the mediaserverd daemon. mediaserverd also gets killed by the system frequently when it’s not responding, even for a short time. It’s not common: these signs are telling us that mediaserverd is in-charge of something important.
When a process switches to the background, mediaserverd will get notified and revoke camera access for that particular process. We need to find a way to make mediaserverd do nothing when it detects that the process is running in the background.
Following a brief research we found that it is possible to prevent mediaserverd from revoking camera access by hooking into an Objective-C method -[FigCaptureClientSessionMonitor _updateClientStateCondition:newValue:], so no code overwriting is required.
To inject into mediaserverd, we used lldb. Lldb does not rely on task-port, instead it calls the kernel for code-injection. In reality, threat-actors that already have kernel code execution capabilities can replace the “entitlements” of mediaserverd to perform such injection.
POC source code is available here.
And on Mac…
From previous experiments back in 2015, the green light next to the front camera on a Mac, cannot be turned off using software only. Modifying the AppleCameraInterface driver and uploading a custom webcam firmware did not do the trick.The green light cannot be turned off since it lights when the camera is powered on. The light remains on as long as there’s power. Hardware-based indicators are ideal from a privacy perspective. We have not validated this on recent Mac versions / HW.
We made a demonstration, accessing the camera/microphone from the background process, and streaming video/audio using the RTMP protocol, steps:
- Set up RTMP server
- Compile mediaserver_patch, inject the code into mediaserverd
- Compile ios_streaming_cam, re-sign the binary with following entitlements and run it in the background
<?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.security.container-required</key> <false/> <key>platform-application</key> <true/> <key>com.apple.private.tcc.allow</key> <array> <string>kTCCServiceMicrophone</string> <string>kTCCServiceCamera</string> </array> <key>com.apple.security.iokit-user-client-class</key> <array> <string>IOSurfaceRootUserClient</string> <string>AGXDeviceUserClient</string> </array> <key>com.apple.private.mediaexperience.suppressrecordingstatetosystemstatus</key> <true/> <key>com.apple.private.mediaexperience.startrecordinginthebackground.allow</key> <true/> <key>com.apple.private.avfoundation.capture.nonstandard-client.allow</key> <true/> </dict> </plist>