SUBVERTING APPLE GRAPHICS: PRACTICALAPPROACHES TO REMOTELY GAINING ROOT
Liang Chen (@chenliang0817)
Qidan He (@flanker_hqd)
Marco Grassi (@marcograss)
Yubin Fu (@fuyubin1993)

About us• Tencent KEEN Security Lab (Previously known as KeenTeam)• 8 Pwn2Own winners in 3 years• Mobile Pwn2Own 2013 iOS, Pwn2Own 2014 OS X, Pwn2Own 2014 Flash,
Pwn2Own 2015 Flash, Pwn2Own 2015 Adobe Reader, Pwn2Own 2016 Edge,
Pwn2Own 2016 OS X * 2• We pwn OS X twice in Pwn2Own 2016 with root privilege escalation• KeenLab with Tencent PC Manager (Tencent Security Team Sniper)
won “Master of Pwn” in Pwn2Own 2016

Agenda• Apple Graphics Overview• Userland Attack Surface• Kernel Attack Surface

Apple Graphics Overview

Apple graphics architecture
Sandboxed App

WindowServer Service

User land
Graphics
User land
Kernel land

IGAccelSurface

IGAccelGLContext…

IGAccelVideoContext

IOAcceleratorFamily2

Nvidia Graphics
Implementation

Intel Graphics
Implementation

Why graphics?
• On OS X, stored in/System/Library/Frameworks/WebKit.framework/Versions/A/Resources/com.apple.
WebProcess.sb• On iOS, binary file embed in kernel:• Sandbox_toolkit：
https://github.com/sektioneins/sandbox_toolkit• What’s in sandbox profile:• File opration• IPC• IOKit• Sharedmem• Etc.

Graphic components allowed in Safari sandbox profile• Userland:
Com.apple.windowserver.active• Apple Graphics usermode daemon• Manage
window/shape/session/workspace, etc.
• Running as _windowserver context

Graphic components allowed in Safari sandbox profile• Kernel• (iokit-connection "IOAccelerator")• iokit-connection allows the sandboxed
process to open all the userclient
under the target IOService(much lessrestrictive than iokit-user-client-class )UserClient NameType

IGAccelSurface

0

IGAccelGLContext

1

IGAccel2DContext

2

IOAccelDisplayPipeUserClient
2

4

IGAccelSharedUserClient

5

IGAccelDevice

6

IOAccelMemoryInfoUserClient

7

IGAccelCLContext

8

IGAccelCommandQueue9

IGAccelVideoContext

0x100

Userland Attack Surface

MIG overview• Apple’s IPC implementation

mach_msg_header_t msg• msg is the key to send message to another process• Msgh_bits• Simple message if 0x00xxxxxx• Complex(descriptor) message if 0x8xxxxxxx

Simple message + 3 types of descriptor• Simple message• Easy to understand• Port descriptor• Send a port to the remote process• Similar to DuplicateHandle in Windows (can be seen in Chrome sandbox)• OOL descriptor• Send a pointer to the remote process• OOL Port descriptor• Send a pointer containing an array of ports to the remote process

WindowServer overview• Two private framework:• CoreGraphics• QuartzCore• Safari sandbox allows to open com.apple.windowserver.active service• Implemented by CoreGraphics framework• QuartzCore framework not allowed by Safari sandbox, but…

CoreGraphics API• Client side API• Starts with CGSxxxx• Service side API• Starts with __XCoreGraphics API grouping• Workspace• Window• Transitions• Session• Region• Surface• Notifications• Hotkeys• Display• Cursor• Connection• CIFilter• Event Tap• MiscThinking as a hacker• Before OS X Lion, no apple sandbox• But there is WindowServer• From OS X Lion, apple sandbox is introduced• What we can do to WindowServer service with sandbox by easy
thinking?
• Move mouse position – Yes, by calling _XWarpCursorPosition• Click – Yes, by calling event tap APIs like __XPostFilteredEventTapDataSync• WindowServer will then call IOKit IOHIDFamily to handle the event• Set hotkey – Yes, by calling _XSetHotKey

Bypass sandbox• Move mouse + click == bypass sandbox• Set hotkey == bypass sandbox• After Apple sandbox is introduced, whole windowserver.active API isallowed by safari, Apple might forget to enhance windowserver?

Reality• You are wrong, Apple is not that bad• Move mouse – Still allowed• Click – checked, no way from sandbox• SetHotKey – checked, no way fromsandboxHow about Window related API• Why thinking about Window API• Easy to cause UAF issues (in MS
Windows)• Connection_holds_rights_on_windowcheck• Only the window creator holds thiswriter• Some tricks to bypass this check inhistory• Many other API doesn’t have thischeck, worthwhile for further
research (Fuzzing, code auditing)Why windowserver?
• Running in root? No• Running in user account? No• It is running in _windowserver• _windowserver is nothing, nothing, nothing_windowserver 174 6.6 0.8 6910400 67708 ?? Ss 三07下午
69:35.83/System/Library/Frameworks/ApplicationServices.framework/Framewo
rks/CoreGraphics.framework/Resources/WindowServer -daemon

But, WindowServer is privilege chameleon !

CVE-2014-1314: Design issue• Session related API• _XCreateSession• Create a new login session• Fork a new process• By default is/System/Library/CoreServices/loginwindow.a
pp/Contents/MacOS/loginwindow• But user can specify the customized login
path by sending a mach message• The forked process will be setuid to thecurrent user’s context• Wow, we bypassed sandbox and run a subprocess under user’s context, outside
sandbox!

CVE-2014-1314: the fix• Deny all request from a sandboxed process to call _XCreateSession• Effective, no way to bypass• Sandbox_check everywhere, makes me tired…It is obvious that Apple realized it isdangerous in CoreGraphics

QuartzCore – The hidden interface• What is QuartzCore?

• Also known as CoreAnimation• More complex graphics operation• Animation• Multi-layer handling• But… Safari sandbox doesn’t allow open com.apple.CARenderServer• Challenge? Sandbox doesn’t allow == we cannot open?
• If you think yes, you stop here
• If you think no and make it open, then you own a new territory.

• Chrome JS renderer cannot open any file, but in fact it can do operation incache folder, why?
• Duplicate a handle !

QuartzCore – The hidden interface• Another way is: a port descriptor message!
• Yes, that is CGSCreateLayerContext• It sends a mach_msg to WindowServer• __XCreateLayerContext handles the request in
WindowServer• Open a port of com.apple.CARenderServer• Send a reply message with a port descriptor toclientSandboxed process calls
CGSCreateLayerContext

WindowServer server creates
QuartzCore Client Port

Send the message back with port
descriptor• Yay, we got the QuartzCore - Running at a
separate and new thread in WindowServer
Sandboxed process obtains the Port

QuartzCore – a new territory• No sandbox check• Nothing…
• 3 minutes code auditing, I find something…

CVE-????-???? Logic issue• In _XSetMessageFile• Can specify arbitrary file path
• And append content to that file
• Content cannot be controlled• No use?

Chameleon – Now I want you to be root!

CVE-2016-1804 : UAF in multi-touch (Pwnie 2016 Nomination)• Misc API in CoreGraphics:_XSetGlobalForceConfig• Introduced for force touch purpose• Newly introduced API is easier tocause problem• In _mthid_unserializeGestureConfiguration it
called CFRelease to free the CFData• After that, the CFData is freed again• Double freeExploitable?
• Problems to be solved• Fill in the controllable data between two FREEs• Especially the first 8 bytes of the CFData• Heap spraying in 64bit process / info leak
• First 8 bytes pointing to the user controllable data (vtable like object)• ASLR• ROP

Exploitation of CVE-2016-1804: Fill in the data
• Looks like hard
• Two frees too close• No way to fill in between the two frees in the same thread• Race condition?
• All CoreGraphics server API runs in a server loop at a single thread (Gated andqueued)• What happened if race failed? (Crash? Of course! Of course! Are you sure)• Give up? (Yes, we give up this vulnerability for quite some days)

An interesting and legacy double free problem• If this is the case

• If the case is like this• Result is:• No crash!

• First 8 bytes of CFData unchanged• Windows LFH like• time window too small,crashed in case of race
failure• Means we can try again andagain until successful• CoreGraphics server APIs are allprocessed in a single thread…
• Any other way

QuartzCore - The hidden interface• Yes, we need hidden
interface’s help
• That is, QuartzCore• QuartzCore server
APIs are singled
threaded also but it isa separate thread
against CoreGraphics

Thread1: CoreGraphics

Thread2:QuartzCore

First Free

Fill

Double Free

Allocate memory
with same sizeNext question? What server APIs you choose to fill in data
• APIs must meet the following criteria:• Create some structure that size is 0x30 (same as CFData)• Every byte of the 0x30 structure can be controlled (Or at least the first 8 byte)• What kind of message you choose?
• Simple message? Of course not, at least the first 8 byte cannot be controlled
fully.
• Port descriptor? Of course not.
• OOL descriptor? Yes, because it allows specifying a pointer to a buffer andpass to the remote process.

Bad news once more
• How many APIs in QuartzCore accepts
OOL descriptor?
• Only one…
• That is _XRegisterClientOptions (It accepts 3
port descriptor followed by an OOL
descriptor)

What’s in _XRegisterClientOptions• Accept a serialized PropertyList(Same concept as List vs JSON)• What is CFPropertyList?
• Check what Apple says
• Can be CFData, CFString, CFArray,
CFDictionary, CFDate, CFBoolean,
and CFNumber• Which one we choose?
• Of course CFDictionary,because this API only accepts
CFDictionary as valid data
• So CFArray also goodAgain, what structure to fill in• First thinking• Use CFDictionary and put many CFData/CFString into the CFDictionary(Because you can control content of CFData/CFString)• Bad news: CFData not good because itself is 0x30 in length, the first 8 bytes
struct CFData itself is not controllable, but only its content. Reduce thereliability by half
• Worse news: Only CFMutableData and CFMutableString have separate
controlled buffer. Deserialized CFxxxx are not mutable, which the controlled
data is inlined… (Except for large data, but those are not good to fill in 0x30
data)

Our last hope
• Rely on CFPropertyListCreateWithData• Cannot rely on CFData/CFString• What if the CFPropertyListCreateWithData
creates some internal struct and free it• Also useful?

• Ok, let’s focus on
CFPropertyListCreateWithData
implementation• Wow, it is open sourced!

What is CFPropertyListCreateWithData• Deserialization logic• Parse serialized buffer data andtransform to basic CFxxxx structures• A complicated implementation withrecursive functions• _CFPropertyListCreateWithData >__CFTryParseBinaryPlist ->__CFBinaryPlistCreateObjectFiltered• CFBinaryPlistCreateObjectFiltered• Token parsing

Oh, Unicode saves the world again!
• Case kCFBinaryPlistMarkerUnicode16String• A temp buffer is allocated and freed after
processing
Allocate the buffer, sizeuser controlled
Copy the user
controlled data to thebufferFree the bufferExploitation of CVE-2016-1804:Fill in the data
• Wrap up:• Create thread 1, triggering thevulnerability again and again• Create thread 2, send a request to_XRegisterClientOptions• With a CFDictionary/CFArray full ofcontrolled Unicode CFString• CFStringCreateWithCharacters creates
Unicode16 CFString

Exploitation of CVE-2016-1804:Fill in the dataExploitation of CVE-2016-1804:Heap spray• A simple test
• Run it 3 times• The 5th byte is random..
• It means you need 256*4G for reliable heap spray• Bad…

Exploitation of CVE-2016-1804:Heap spray• Another test
• Run it 3 times• 5th byte always 0x1• Spraying will be very reliableExploitation of CVE-2016-1804:Heap spray• Strategy is different• Need persistent in memory• Need to allocate large block of memory (Memory is less randomized)• Both CoreGraphics API and QuartzCore API are good candidate• Something is same
• Need to pick up a OOL descriptor message

Exploitation of CVE-2016-1804:Heap spray• CGXSetConnectionProperty is agood candidate• Get the CFDictionary object fromglobal, if not exist then create• Set the key/value pair according touser’s input• Can set the value many times bysending multiple messages where
keys are differentExploitation of CVE-2016-1804: ASLR / Codeexecution• ASLR is easy as it shares the same base address with Safari webkit• Code execution:• http://phrack.org/issues/66/4.html• ROP

Exploitation of CVE-2016-1804: Root?
• Wait wait, we got only _windowserver context?
• Really? Nono
• We can setuid to current user as we get code
execution, just similar as CVE-2016-1314• Why not setuid and setgid to 0? Crazy! Let‘stry…
• Successful…
• Why?
• _windowserver is process euid, uid is still root!

• Three bugs , three different privilege
obtained… So I call it Chameleon.

DemoKernel Attack Surface

The IOAccelSurface Family• IOAccelSurface family plays an important role in Apple's Graphics
Driver System• However the interface was originally designed for WindowServer use
solely and vulnerabilities are introduced when normal processes cancall into this interface• CVE-2016-1815 – `Blitzard` our p2o bug

Key Functions• Set_id_mode• The function is responsible in initialization of the surface. Bitwised presentation typeflags are specified, buffers are allocated and framebuffers connected with thissurface are reserved. This interface must be called prior to all other surface
interfaces to ensure this surface is valid to be worked on.

• surface_control• Basic attributes for the current surface are specified via this function, i.e. the flushingrectangle of current surface.

• surface_lock_options• Specifies lock options the current surface which are required for following
operations. For example, a surface must first be locked before it's submitted for
rendering.

• surface_flush• Exchange backup and current buffer. Triple buffering is enabled for certain surfaces.

Basic render unit
• The basic representing region unit in IOAccelerator subsystem is a 8
bytes rectangle structure with fields specified in surface_control
function.
• int16 x;• int16 y;• int16 w;• int16 h;Typical Graphics Pipelineset_scale and submit_swap• The surface’s holding drawing region can be scaled and combinedwith the original rectangle region to form a rectangle pair, rect_pair_t• The drawing region specified in surface_control is represented in
int16• After scaling it’s represented as IEEE.754 float.

• Submit_swap submits the surface for rendering purpose and it will
finally calls into blit opertion.

Blit_param_t• The pair and blit_param_t from submit_swap will be passed toblit3d_submit_commands.
• The two most interesting fields are two ints at offset 0x14 and 0x34,
which is the current and target (physical) surface’s width and height.

Blit3d_submit_commands• Different incoming surface are cropped and resized and merged tomatch the display coordinate system with calculated scaling factor.
• After normalization two flushing rectangles are submitted to GPU via
BlitRectList

Overflow in blit3d_submit_commands• The OSX graphics coordinate system only accepts rectangles in range[0,0,0x4000,0x4000] to draw on the physical screen• However a logical surface can hold rectangle of negative coordinate
and length.
• represented by a signed 16bit integer, translates to range [-0x8000, 0x7fff].

• The blit function needs to scale the logical rectangle to fit it in thespecific range.

blit3d_submit_commands check for current surface's width and target surface's height.
If either of them is larger than 0x4000, Huston we need to scale the rectangles now.
• a vector array is allocated with size height/0x4000 hoping to store the scaled output
valid rectangles.
• The target surface's height always comes from a full-screen resource, i.e. thephysical screen resolution. Like for non-retina Macbook Air, the height will be 900.
• As non mac has a resolution of larger than 0x4000, the vector array's length is fixed
to 1.

Rectangle transformations on X axis

I believe you won’t want to read this…
• Decompiled blit3d_submit_commands function

Rewritten as IDA hex-rays cannot properly handle SSE floating point
instructions

Rewritten as IDA hex-rays cannot properly handle SSE floating point
instructions

OOB leads the way• The code implicitly assumes that if the width is smaller than 0x4000,
the incoming surface's height will also be smaller than 0x4000, whichis the case for benign client like WindowServer, but not sure for funkyclients.
• By supplying a surface with rect2.x set to value larger than 0x4000,
LINE1 will perform access at vector_array[1], which definitely goesout-of-bound with function IGVector::add called on this oob location,

Determine the surface attributes• By supplying size (0x4141, 0x4141, 0xffff, 0xffff) for surface andcarefully prepare other surface options, we hit the above code pathwith rectangle (16705, 16705, -1, -1).
• These arguments will lead to out-of-bound access at vec[1]• After preprocessing, the rectangle is transformed to y 16705, x 321,
height -1, len -1, triggering one oob write• Then bail out in while condition in next loop

Revisit the IGVector• struct IGVector{• int64 currentSize;• int64 capacity;• void* storage;•}• The vulnerable allocation of blit3d_submit_commands allocation falls
at kalloc.48, which is crucial for our next Heap Feng Shui.

Heap Fengshui in kalloc.48• kalloc.48 is a zone used frequently in Kernel with IOMachPort acting
as the most commonly seen object in this zone and we must get rid ofit• Previous work mainly comes up with openServiceExtended andvm_map_copy to prepare the kernel heap.
• However these are not suitable for our exploitation

Heap Fengshui in kalloc.48 (cont.)• ool_msg spray has small heap side-effect• but vm_map_copy’s head 0x18 bytes is not controllable while we need
control of 8 bytes at the head 0x8 position• io_open_service_extended has massive side effect in kalloc.48 zone
by producing an IOMachPort in every opened spraying connection• io_open_service_extended has the limitation of spraying at most 37
items, constrained by the maximum properties count per
IOServiceConnection can hold
• The more items we can fill, the less side effect we will need to consider

IOCatalogueSendData• The addDrivers functions accepts an OSArray with the following easyto-meet conditions:• OSArray contains an OSDict• OSDict has key IOProviderClass• incoming OSDict must not be exactly same as any other pre-exists OSDict inCatalogue

IOCatalogueSendData (cont.)• prepare our sprayed content in the array part as the XML shows, andslightly changes one char at end of per spray to satisfy condition 3• We only need control of +8-+16 bytes region

Final spray routine in kalloc.48• Spray 0x8000 combination of 1 vm_map_copy and 50
IOCatalogueSendData content of which totally controllable (both ofsize 0x30), pushing allocations to continuous stable region• free vm_map_copys at 1/3 to 2/3 part, leaving holes in allocation• trigger vulnerable function, vulnerable allocation will fall in hole wepreviously left

In a nearly 100% chance the heap will layout as this figure illustrated, which exactly match what we expected.
Spraying 50 or more 0x30 sized controllable content in one roll can reduce the possibility of some other irrelevant
0x30 content such as IOMachPort to accidentally be just placed after free block occupied in.

0xﬀ… 62388000+0x1528
IntelAccelerator…

vm_map_copy…

0xﬀ… 6238a000

0xﬀ… bf800000+0x528

IGAccelVideoCont IGAccelVideoCont
ext
ext

…

vm_map_copy

vm_map_copy

0xﬀ… 62389000+0x140 +0x288

0xﬀ… bf7ﬀ000+0x1140 +0x1288

0xﬀ… bf800000
vm_map_copy header

niddle(filled 0x41414141)

KALLOC.8192 ZONE

0xﬀ… bf801000
filled with 0x41414141

vm_map_copyExploitation: now what?
• We have an arbitrary-write-where but our value written isconstrained.
• For example we can use this 4 byte overwrite with value “0xbf800000”
to do a partial overwrite of the less significant 4 bytes of the “service”
pointer of a IOUserClient.
• This new overwritten pointer will be “0xffffff80bf800000”.
• We control this heap location at ”0xffffff80bf800000”!
BEFORE OOB WRITEAFTER OOB WRITE

A0 00 AD DE 80 FF FF FF

00 00 80 BF 80 FF FF FF

Exploitation: kASLR bypass turning this into a
infoleak• On OS X the kernel is randomized, we need to bypass kASLR.
• Our target IOUserclient is of type IGAccelVideoContext• We overwrite the “accelerator” field of this userclient (offset 0x528),
like explained in the previous slide pointing it to our controlled
location• We then abuse the external method
IGAccelVideoContext::get_hw_steppings to leak 1 byte to userspace,to read a vtable 1 byte at a time.
• With the vtable address we follow it to read a TEXT address(OSObject::release) to finally get the kASLR slide, bypassing it.

Exploitation: kASLR bypass turning this into a
infoleak (2)IGAccelVideoContext::get_hw_steppings( __int64
this, _DWORD *a2) {…
__int64 accelerator = *(this + 0x528); //accelerator is 0xffffff80bf800000...
a2[3] = *(unsigned __int8 *)(*(_QWORD*)(accelerator + 0x1230) + D0); // this isreturned to userspace!
…
}Exploitation: 1 byte infoleak
memory layout

Exploitation: rebasing and ROP Chain• Now with the kASLR slide we can dynamically rebase our ROP Chainthat we use for kernel code execution.
• At the end of the ROP chain we will abuse kern_return_t
KUNCExecute(char executionPath[1024], int uid,
int gid) to spawn a arbitrary executable as root in userspace,
bypassing all the mitigations (SMEP/SMAP, SIP)
• Spawn a root OS X Calculator for teh lulz! Microsoft Windows
calculators sucks :D

Exploitation: gaining RIP control• The last missing piece of the puzzle is to get RIP control and execute
our ROP payload in kernel and gain kernel codexec• We will again abuse a IGAccelVideoContext and his superclass
IOAccelContext2.
• If you recall from the previous slides, we corrupted a pointer at offset
0x528 to point to our controlled location.
• We choose then to target another method, named “context_finish”,
which will make a virtual function call that we can totally control.
• RIP Control is achieved and we start executeExploitation: gaining RIP control (2)IOAccelContext2::context_finish
push rbp
mov rbp, rsp…
mov rbx, rdi //thismov rax, [rbx+528h] // rax is a location with
controlled content…
call qword ptr [rax+180h] // RIP control…

Exploitation
You can check more details of the exploitation in our WhitePaper
Unfortunately here we have time and space constraints
But now.. A COOL DEMO J!
An interesting fact is that you cannot pop a root Calc by sudoJ
If you see a root Calc on your system, u’re doomed by kernel exploit L

DemoAcknowledgements• Wushi• Windknown• Luca Todesco• UfotalentThank you!