Exploit-DB #44307
Description: Google Software Updater macOS - Unsafe use of Distributed Objects Privilege Escalation
Type: local
Platform: macos
Date: 2018-03-20
Author: Google Security Research

Exploit:
/*
Google software updater ships with Chrome on MacOS and installs a root service (com.google.Keystone.Daemon.UpdateEngine)
which lives here: /Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/MacOS/GoogleSoftwareUpdateDaemon

This service vends a Distributed Object which exposes an API for updating google software running on the machine.

Distributed Objects are very very hard to safely use across a privileged boundary.

The GoogleSoftwareUpdateDaemon process attempts to "sanitize" objects passed to it by serializing
and deserializing them to a plist, however this still means we can attack the plist serializing code!

Specifically, with D.O. we can pass proxy objects which allow us to overload all objective-c
method calls. We can make the plist code think it's serializing a CFString, and then change our behaviour
to return a different CFTypeID so we become a dictionary for example.

The plist serialization code is not written to defend against such proxy objects, because D.O. should not be
used across a privilege boundary.

In this case I'm targetting the following code in CoreFoundation:

static void _flattenPlist(CFPropertyListRef plist, CFMutableArrayRef objlist, CFMutableDictionaryRef objtable, CFMutableSetRef uniquingset);

plist will be a proxy for the FakeCFObject I define. We can first pretend to be a CFString to pass some other type checks, then become a CFDictionary
(by simply returning a different return value for the _cfTypeID method.) We can then reach the following code:

    CFIndex count = CFDictionaryGetCount((CFDictionaryRef)plist);
    STACK_BUFFER_DECL(CFPropertyListRef, buffer, count <= 128 ? count * 2 : 1);
    CFPropertyListRef *list = (count <= 128) ? buffer : (CFPropertyListRef *)CFAllocatorAllocate(kCFAllocatorSystemDefault, 2 * count * sizeof(CFTypeRef), __kCFAllocatorGCScannedMemory);
    CFDictionaryGetKeysAndValues((CFDictionaryRef)plist, list, list + count);
    for (CFIndex idx = 0; idx < 2 * count; idx++) {
      _flattenPlist(list[idx], objlist, objtable, uniquingset);
    }

Since we're not a real CFDictionary we can return an arbitrary value for count. If we return a value < 0 it will be used to calculate the size of a stack buffer.
By passing a carefully chosen value this lets you move the stack pointer down an arbitrary amount, off the bottom of the stack and potentially into another thread's stack
or on to the heap, allowing memory corruption.

There will be dozens of other places where attack-controlled proxy objects will be able to interact with system code that was not written expecting to have
to deal with proxy objects.

The correct fix is to not use Distributed Objects across a privilege boundary, as per Apple's advice:
https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/DesigningDaemons.html

build this PoC:
clang -o ks ks.m -framework Foundation -framework CoreFoundation

start lldb waiting for the daemon to start:
sudo lldb --wait-for -n "/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/MacOS/GoogleSoftwareUpdateDaemon"

continue lldb and run the poc, you should see that the stack ends up pointing well outside the stack :)
*/

/*
ianbeer
Google software updater LPE on MacOS due to unsafe use of Distributed Objects

Google software updater ships with Chrome on MacOS and installs a root service (com.google.Keystone.Daemon.UpdateEngine)
which lives here: /Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/MacOS/GoogleSoftwareUpdateDaemon

This service vends a Distributed Object which exposes an API for updating google software running on the machine.

Distributed Objects are very very hard to safely use across a privileged boundary.

The GoogleSoftwareUpdateDaemon process attempts to "sanitize" objects passed to it by serializing
and deserializing them to a plist, however this still means we can attack the plist serializing code!

Specifically, with D.O. we can pass proxy objects which allow us to overload all objective-c
method calls. We can make the plist code think it's serializing a CFString, and then change our behaviour
to return a different CFTypeID so we become a dictionary for example.

The plist serialization code is not written to defend against such proxy objects, because D.O. should not be
used across a privilege boundary.

In this case I'm targetting the following code in CoreFoundation:

static void _flattenPlist(CFPropertyListRef plist, CFMutableArrayRef objlist, CFMutableDictionaryRef objtable, CFMutableSetRef uniquingset);

plist will be a proxy for the FakeCFObject I define. We can first pretend to be a CFString to pass some other type checks, then become a CFDictionary
(by simply returning a different return value for the _cfTypeID method.) We can then reach the following code:

    CFIndex count = CFDictionaryGetCount((CFDictionaryRef)plist);
    STACK_BUFFER_DECL(CFPropertyListRef, buffer, count <= 128 ? count * 2 : 1);
    CFPropertyListRef *list = (count <= 128) ? buffer : (CFPropertyListRef *)CFAllocatorAllocate(kCFAllocatorSystemDefault, 2 * count * sizeof(CFTypeRef), __kCFAllocatorGCScannedMemory);
    CFDictionaryGetKeysAndValues((CFDictionaryRef)plist, list, list + count);
    for (CFIndex idx = 0; idx < 2 * count; idx++) {
      _flattenPlist(list[idx], objlist, objtable, uniquingset);
    }

Since we're not a real CFDictionary we can return an arbitrary value for count. If we return a value < 0 it will be used to calculate the size of a stack buffer.
By passing a carefully chosen value this lets you move the stack pointer down an arbitrary amount, off the bottom of the stack and potentially into another thread's stack
or on to the heap, allowing memory corruption.

There will be dozens of other places where attack-controlled proxy objects will be able to interact with system code that was not written expe