Forget the NSA, it's Shazam that's always listening!
› reversing Shazam to uncover why it never relinquishes the internal microphone on macOS
11/14/2016
TL;DR When Shazam (macOS) is toggled 'OFF' it to simply stops processing recorded data...however recording continues 😟
note:
1) I'm conflicted on whether or not this is a big deal. On one hand, even when you click 'OFF' Shazam continues to consume audio off the internal microphone. On the other hand, they don't appear to process or use this data in any way. Still, 'OFF' should mean off ... and due to their actions, we could get creative an easily design a piece of malware that steals this recoding without having to initiate a recording itself (which would likely generate an alert).
To avoid complex ethical discussions this blog aims to simply be a technical analysis of the app.
2) I delayed publishing this blog in order to contact Shazam. They recently responded, confirming my findings. Their full response is included at the end of this writeup.
Background
Recently I presented a novel attack which illustrated how advanced Mac malware could piggy-back onto legitimate webcam sessions in order to surreptitiously record the local user. In order to thwart this attack, I released a new tool named 'OverSight'.
OverSight is a free utility that alerts a user whenever a process accesses the webcam, or the mic internal microphone is activated. With approximately 50,000 downloads, OverSight has proven quite popular!
While I design tools such as OverSight with the goal of protecting users against novel attacks and/or advanced yet-to-be-discovered malware, such tools can also provide insight into the actions of legitimate tools. Here, we'll discuss what appears to be some "interesting" behavior in one such application.
So this all starts with an email from an OverSight user (thanks Phil!) who pinged me, stating, "Thanks to Oversight, I was able to figure out why my mic was always spying on me. Just to let you know, the Shazam widget keeps the microphone active even when you specifically switch the toggle to OFF in their app. Scary."
...I was intrigued, but alas too busy to immediately investigate. Until now! Having an 11+ hr flight to Argentina to present at EkoParty seemed like the perfect time to dive into a deep reversing session. I mean, when else do you have such a large chunk of pristine time without the distraction of the internet, work emails/IMs, or other humans!?
To start, I grabbed the application from the Mac App Store. Ah, what a prophetic tag line: "Shazam is here to lend its ears to your Mac."
Once installed, Shazam automatically begins listening for music, "ready to name that tune at a moment's notice." This song identification or "auto tagging" (in Shazam's parlance) is of course is the main functionality of the tool.
As expected, OverSight will detect the fact that a process, namely Shazam, is using the internal microphone (yes this is a beta of the next version that can identify the process):
Most (security-conscious) users probably don't want Shazam listening all the time. Shazam appears to oblige, seemingly providing an option to disable this listening:
However, sliding the selector to 'OFF' did not generate the expected, "Mic was deactivated" OverSight alert. Odd :\ ...though this did match what the OverSight user reported to me.
My first thought was perhaps OverSight had 'missed' the Mic deactivation, or contained some other bug or limitation. However testing seemed to confirm that OverSight works as expected. For example, when one quits (exits) Shazam, OverSight does receive a "Mic Deactivation" notification from the OS, and alerts this fact to the user:
So is Shazam still listening even when the user attempts to toggle it to 'OFF'? One way to find out - let's reverse the app!
Reversing Shazam
Once downloaded from the Mac App Store, Shazam is installed into the /Applications folder. Poking around we can see it contains a Login Item (to ensure it started automatically each time a user logs in), as well as various frameworks (e.g. ShazamSDK.framework):
We'll start by reversing the main application bundle with the goal of determining what happens (or equally important what doesn't happen) when the user toggles Shazam to 'OFF'
Due to the verbosity of Objective-C, when reversing applications (such as Shazam) that are written in this language, it's easy to identify methods of interest. For example, Shazam contains a method named, "toggleAutoTagging" that belongs to a "SHMainViewController" class.
Starting Shazam under a debugger (lldb), we can set a breakpoint on this method. And sure enough, when we toggle the 'ON'/'OFF' switch in Shazam's drop down menu in the status bar, the breakpoint is hit:
* stop reason = breakpoint 1.1
frame #0: 0x000000010000914c Shazam`-[SHMainViewController toggleAutoTagging:]
-> 0x10000914c <+0>: pushq %rbp
0x10000914d <+1>: movq %rsp, %rbp
0x100009150 <+4>: pushq %r15
0x100009152 <+6>: pushq %r14
The ABI (read: calling convention) for Objective-C method calls is as follows:
- The first argument: $RDI, the Objective-C object
- The second argument: $RSI, the name of the selector (method) being invoked
- The third argument: $RDX, the first parameter to the method
Let's dump these values for -[SHMainViewController toggleAutoTagging:] which we've just broken on in the debugger:
(lldb) po $rdi
<SHMainViewController: 0x10199d2e0>
(lldb) x/s $rsi
0x10001f72d: "toggleAutoTagging:"
(lldb) po $rdx
<ITSwitch: 0x107c37a00>
(lldb) p (BOOL)[$rdx isOn]
(BOOL) $5 = NO
So we've identified the code that is invoked whenever Shazam is toggled 'ON' or 'OFF.' Now, let's press on, with the goal of determining what happens when the user toggles Shazam to 'OFF'.
A "cleaned up" decompilation of the 'toggleAutoTagging:' method shows what happens in code, when the slider is toggled to 'OFF':
void -[SHMainViewController toggleAutoTagging:]
{
//will execute when user toggles to 'OFF'
if([rbx isContinuousTaggingRunning] != 0x0)
{
rbx = [[r14 applicationConfiguration] retain];
[rbx setUserDisabledTagging:0x1, rcx];
rbx = [[r14 tagManager] retain];
[rbx stopTagging];
}
}
In short, if the user is toggling Shazam to 'OFF', the code will end up calling the 'stopTagging' method of the SHAppTagManager class. Seems reasonable, ya?
Reversing the -[SHAppTagManager stopTagging] method reveals it simply gets a reference to an object of type 'SHKTaggingInterruptController' and then invokes its 'stopTagging' method.
The SHKTaggingInterruptController class is implemented in the ShazamSDK.framework dynamically linked shared library. Examining this class with class-dump reveals some interesting methods:
$ classdump /Applications/Shazam.app/Contents/Frameworks/ShazamSDK.framework/ShazamSDK
@interface SHKTaggingInterruptController : NSObject...
- (BOOL)shouldContinueRecordingWhenBackgrounded;
- (void)stopTagging;
- (void)stopRecording;
- (void)startRecording;
Seems reasonable to think 'stopRecording' would be called when the user toggles Shazam to 'OFF'? We'll get to that shortly...
For now, let's not stray, and continue by looking at the implementation of the 'stopTagging' method:
void -[SHKTaggingInterruptController stopTagging]
{
...
[self stopTaggingForReason:0x2 withError:0x0 tagContext:0x0];
}
Basically it just calls into the 'stopTaggingForReason: withError: tagContext:' method.
Invoking this method, results in the execution of -[SHKTaggingInterruptController stopTaggingCommon:]. Peeking at the decompilation of this method finally reveals some code that appears to cease recording:
//check if recording should stop
r13 = (rbx, @selector(shouldStopRecordingWhenTaggingEnds));
if (r13 != 0x0)
{
[r14 stopRecording];
}
Reversing the 'stopRecoding' method, reveals it calls into a class named 'audioRecorder', invoking its 'stopRecording' method. This method (by means of a block), invokes the 'AudioOutputUnitStop()' function.
int ___33-[SHKAudioRecorder stopRecording]_block_invoke(int arg0)
{
...
rbx = [[*(arg0 + 0x20) audioConfigurator] retain];
r15 = AudioOutputUnitStop([rbx rioUnit]);
}
The 'AudioOutputUnitStop()' function is an Apple function that, "stops an I/O audio unit, which in turn stops the audio unit processing graph that it is connected to." Further googling confirms that yes, as expected this is how one stops recording.
However if we take a step back, we can see the 'stopRecording' method will only be executed if the 'shouldStopRecordingWhenTaggingEnds' method returns YES (i.e. not 0x0). So what does this method do? Reversing, reveals it simply returns a BOOL (YES/NO) if a 'taggingType' is set to 0x2 or not:
char -[SHKTaggingOptions shouldStopRecordingWhenTaggingEnds]
{
rax = [self taggingType];
rax = (rax == 0x2 ? 0x1 : 0x0) & 0xff;
return rax;
}
But, but, but...if we examine the 'taggingType' in a debugger, guess what!? It is not set to 0x2:
* stop reason = breakpoint 2.1
frame #0: 0x000000010019d18b ShazamSDK`-[SHKTaggingOptions shouldStopRecordingWhenTaggingEnds]
p (int)[$rdi taggingType]
(int) $17 = 1
Why does this matter? Well this means that the 'shouldStopRecordingWhenTaggingEnds' will return 'NO' meaning the call to 'stopRecording' will not be invoked!
Ok time, for a brief recap. In short, when the user toggles Shazam to 'OFF' the call to the 'stopRecording' method is skipped since some 'taggingType' type is set to 0x1 (instead of 0x2). I have no idea why.
if (r13 != 0x0)
{
//this code is never executed, since tagging type is not 0x2
[r14 stopRecording];
}
Can we determine where this tag type is set? As maybe it's dynamically set, and thus could be 0x2 in some scenarios (meaning Shazam would indeed stop listening when the user toggles it off).
If we begin in the -[SHStartUpManager finishApplicationStartup] method, we can determine it calls -[SHAppTagManager startContinuousTagging]. This method invokes the 'startTaggingWithType:' method with an 0x1. The tag type!
void -[SHAppTagManager startContinuousTagging]
{
rbx = [[self taggingController] retain];
[rbx startTaggingWithType:0x1];
...
}
So the tag type is hard-coded to 0x1. Things aren't looking good for Shazam :/ Does it really not disable recording when the user toggles the app to 'OFF' in the UI?
I'll be the first to admit that maybe there is other logic I missed that disables Shazam's listening functionality when it's set to 'OFF' in the UI.
To try to dispel this notion, I set breakpoints on the 'stopRecording' method as well Apple's AudioOutputUnitStop() function. Though I toggled Shazam on and off multiple times, with and without music playing, neither breakpoint was ever hit - meaning, Shazam never invoked this code :/ The only time I could get Shazam to invoke the 'stopRecording' logic was when my MacBook went to sleep:
* stop reason = breakpoint 3.1
frame #0: 0x0000000100194c6e ShazamSDK`-[SHKTagManager stopRecording]
frame #1: 0x0000000100187c46 ShazamSDK`-[SHKTaggingInterruptController stopRecording] + 48
frame #2: 0x00000001001a8d86 ShazamSDK`-[SHKApplicationLifeCycleManager_Mac receivedSleepNotification:] + 55
Still unconvinced (hey, I'm a skeptical person), I dug around more in Shazam's code. Specifically I took a look at the 'startRecording' method. In this method is some code that checks to see if the app is already recording. This makes sense, as it wouldn't want to (nor shouldn't) invoke the low-level audio recording logic if the app is already recording:
void -[SHAppTagManager startContinuousTagging]
{
//don't start recording if already recording
if ([r14 isRecording] == 0x0)
{
[rbx configureAudio];
[r14 startRemoteIOUnit];
}
...
Toggling Shazam from 'OFF' to the 'ON' state, as expected invokes the 'startRecording' method. Interestingly the 'isRecording' method returns YES ...meaning, well the app is already (or still) recording :/
(lldb) ni
0x1001ab3df <+198>: movq 0x8e2e2(%rip), %rsi ; "isRecording"
(lldb) ni
0x1001ab3e6 <+205>: movq %r14, %rdi
(lldb) ni
0x1001ab3e9 <+208>: callq *0x78f69(%rip) ; (void *)0x00007fff964d4b40: objc_msgSend
(lldb) reg read $rax
rax = 0x0000000000000001
Toggling the app to the 'OFF' state, and manually invoking the 'isRecording' method directly from the debugger, still returns YES:
(lldb) p (BOOL)[0x100729040 isRecording]
(BOOL) $19 = YES
Ok so it appears the app is still recording even when toggled 'OFF' :/ So You might be wondering what the difference between 'ON' and 'OFF' is ...read on!
Whenever recorded audio is ready for consumption, the OS invokes Shazam's 'ShazamRecordingInputCallback' function (which that app previously registered with the OS):
frame #1: 0x00000001001aabd8 ShazamSDK`ShazamRecordingInputCallback + 1302
frame #2: 0x0000000104c3d98c CoreAudio`AUHAL::AUIOProc(unsigned int, AudioTimeStamp const*, AudioBufferList const*, ...) + 2324
frame #3: 0x00007fff8190cd8d CoreAudio`HALC_ProxyIOContext::IOWorkLoop() + 5453
frame #4: 0x00007fff8190b667 CoreAudio`HALC_ProxyIOContext::IOThreadEntry(void*) + 131
frame #5: 0x00007fff8190b38b CoreAudio`HALB_IOThread::Entry(void*) + 75
This function, consumes recorded data off the OS's audio input stream via a call to AudioUnitRender(). Later in this function, the code checks whether an instance of a 'SHKSignatureGenerator' has it's 'generating' variable set. If it is, the recorded audio is fed into the Shazam's signature engine (i.e. to identify the song):
r14 = (r13)(rbx, @selector(generating), rdx, rcx);
if (r14 != 0x0) {
rbx = [(r13)(r15, @selector(signatureGenerator), rdx, rcx) retain];
r14 = (r13)(rbx, @selector(shouldConsumePreRecordBuffer), rdx, rcx);
if (r14 != 0x0) {
(r13)(r15, @selector(setTotalBytesRecorded:), 0x0);
r14 = [(r13)(r15, @selector(audioConfigurator)) retain];
rbx = objc_retainAutorelease([(r13)(r14, @selector(preRecordBuffer)) retain]);
var_38 = (r13)(rbx, @selector(get:), 0x0);
rbx = [(r13)(r15, @selector(audioConfigurator)) retain];
memcpy(*((rbx, @selector(audioConsumerBufferList)) + 0x10), var_38, 0x0);
If the 'generating' flag is not set, the recorded audio is not processed. Setting a breakpoint on the method that is responsible for setting this 'generating' flag, confirms it is invoked as expected, when Shazam is toggled either 'ON' or 'OFF'.
* stop reason = breakpoint 3.1
frame #0: 0x0000000100197f46 ShazamSDK`-[SHKSignatureGenerator setGenerating:]
(lldb) p (BOOL)$rdx
(BOOL) $46 = NO
In other words what 'OFF' appears to mean, is simply, "stop processing the recorded data" ...not cease recording.
Conclusion
Well, my flight is about to land - finally! Thanks for tagging along with me into the bowels of Shazam. I'm confident we uncovered exactly what's going on, and why OverSight correctly never generated a "Mic Deactivation" alert.
Again, though it appears that Shazam is always recording even when the user has toggled it 'OFF' I saw no indication that this recorded data is ever processed (nor saved, exfiltrated, etc). However, I still don't like an app that appears to be constantly pulling audio off my computers internal mic. As such, I'm uninstalling Shazam as quickly as possible!
Update/Vendor Response
Before publishing the blog, I contacted Shazam to get their take on the situation. It took a minute, but they eventually responded:
In short, they agree with my technical analysis and confirm that yes, the Mac App even when 'OFF' is continually recording. Good news is that hopefully, in a future update, this will be "addressed." Hooray...I guess?