Reproducing Ret2's SkyLight fuzzer in MacOS Mojave
In last year’s Pwn2Own, the team at Ret2 Systems developed an interesting exploit chain for MacOS High Sierra through bugs they found in Safari and the MacOS WindowServer. A few months later, they published an excellent walkthrough of their methodology and the bugs used to compromise the machine. Of particular interest to me was their description of how they used Frida and a relatively basic fuzzing strategy to find an exploitable bug in the SkyLight library. I decided to spend some time to reimplement their methodology in Mojave as a way to get some hands on experience with Frida and mach message fuzzing.
Before we get into it, I’d like to thank the team at Ret2 Systems for their help in reproducing their research, and for the original write-up. Often we never get a chance to understand these chains, and the six-part series is a must-read for anyone interested in this kind of stuff. Follow along if you’re interested in the process, but for those who’d rather just get started fuzzing SkyLight you’ll find everything you need here.
While I would recommend reading all six posts in the Ret2 Systems Pwn2Own walkthrough, the most important for our purposes is post four, Cracking the Walls of the Safari Sandbox. In this post Ret2 describes their methodology and why they chose WindowServer, so we’ll skip that here.
intercepting messages
The first step is to identify the points in the SkyLight Library where we will be using Frida Interceptor to read and modify incoming mach messages. To do this, we first need to find the addresses of the MIG subsystems. You can get these addresses by using either jtool, by searching for the string “subsystem” in the library itself using your favorite disassembler.
Visiting each of these offsets in a disassembler, we’ll find cross-referencing code which will reveal the MIG dispatch handlers we want to hook. They may be different in new newer versions of Mojave – the above are from a 10.14 VM.
The first subsystem on our list is __CGXCGXWindowServer_subsystem
, beginning 8 bytes before the jtool-provided offset in the SkyLight library.
All we need to do is ask our disassembler to show us references to the subsystem, and it is simple enough to identify the associated dispatch routine. Note that I am doing this in Hopper, but free alternatives such as radare2 or GHIDRA should work just fine.
After the pointer to the mach message we want to fuzz has been moved into rdi(0xcde63), the mach message handler pointed to by rax will be called. This call instruction is where we want to intercept. Note this first case is special because of a limitation in Frida that prevents us from hooking directly at the call instruction. This is because Frida needs 5 bytes of space after the intercept target to do its relocations, but as the basic block ends here there is no room. Fortunately for us we can just hook one instruction earlier, as at this point rdi already contains the pointer to the message we want to intercept. With the other two subsystems, be sure to hook at the call instruction.
To perform the intercept, we need to note the offset of instruction and the target call register, which in this case would be 0xcde66 and rax. Repeat this process with the two other MIG subsystems identified earlier. Now we can start putting together the JavaScript that will injected into the WindowServer process by Frida.
Save the file, and run it with Frida. Since WindowServer essentially runs as root you’ll need to use sudo. Also note that by default even as root you will not be able to grant Frida permission to attach to the WindowServer process without first disabling System Integrity Protection.
We are now able to intercept the mach messages as they are being passed to their respective dispatch routines.
reading the mach_msg_header_t struct
rdi(arg[0] in our interceptor function) points to the message’s mach_msg_header_t
struct, which has various members that we need to read in order to be able to fuzz the message’s inline data and save information about it for our replay log. The following image describes the structure. Most of the members are simply types that boil down to 32-bit unsigned integers. This image from Chapter 9 of Amit Singh’s Mac OS X Internals gives an overview of the struct.
To read the message, we just use Frida’s readU32() method at the correct offsets within the struct, and readS32() for the msgh_id
member.
fuzzing the inline message data
Now that we’ve read the message, we can fuzz the inline data contained in msgh_buff
and write the fuzzed buffer into memory. You could skip reading the message data if you wish, but in order to make a replay log this information is necessary later on.
As you can see, we’re just doing a single bitflip somewhere in the message as demonstrated by Ret2, but you can of course change this to fuzz however you’d like.
At this point we can start a basic fuzzing session by running sudo frida -l <fuzzer_file.js> WindowServer
as we did before. To keep this post brief I won’t describe how the replays work(big thanks to Markus at Ret2 for pointing me in the right direction on how to do it), but the full fuzz.js file with replay ability can be found on my github repo for this project. It includes the replay mode as well as a separate JavaScript file called driver.js that manages the fuzzing process, including the ability to reattach to WindowServer after a crash and log any exceptions found during fuzzing.
resources
https://blog.ret2.io
http://hurdextras.nongnu.org/ipc_guide/mach_ipc_basic_concepts.html
Chapter 9 on OSX IPC From Amit Singh’s Mac OS X Internals
https://fergofrog.com/code/cbowser/xnu/osfmk/mach/message.h.html#mach_msg_ool_descriptor32_t
https://github.com/uroboro/mach_portal/blob/master/mach_portal/unsandboxer.c
http://blog.wuntee.sexy/reaching-the-mach-layer
http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_msg_header.html