I read Ian Beer’s article about break safari’s sandbox on OS X(link,link). Here I do a summery and also include sandbox security on iOS, and the fuzzing methods on both system.

Safari’s sandboxing model is based on privilege separation. It uses the webkit2 framework to communicate between multiple seperate proesses which collectively form the Safari browser. Safari is split into 4 distinct process families, each of them is responsible for a different part, as shown below:
Break-out-iOS/OS-X-sandbox-Safari-4-processes-image
OS X uses the Mandatory Access Control(MAC) paradigm to implement sandboxing, specially it uses the TrustedBSD framework.
Every process can have a unique sandbox profile.
Sandbox policy files: /System/Library/StagedFrameworks/Safari/WebKit.framework/Versions/A/Resources/**.sb

##OS X kernel fundamental
Three broad sybsystems which collectively are known as XNU:

###BSD
The majority of OS X syscalls are BSD syscalls. for file systems and networking.

###Mach
Originally a research microkernel from CMU, mach is responsible for many of the low-level idiosyncrasies of OS X.
Responsible for IPC machanism and virtual memory management.

  • Mach IPC
    To change the permissions of a memory mapping in your process, talk to a device driver, render a system font, symbolize a crash dump, debug another process or determine the current network connectivity status——- you ‘re sending and receiving mach messages.
    How mach IPC works?
    [img]
  • Mach messages
    [img]
    • out-of-line memory
    • Bi-directional messages
    • Bootstrapping Mach IPC
      The parent of all processes on OS X is , one of its role is to set the default bootstrap port which will then be inherited by every child
    • Launchd
      holds the receive-right to this bootstrap port and plays the role of the bootstrap server, allowing processes to advertise named send-rights which other processes can look up. These are OS X Mach IPC services.
    • MIG mach interface generator
      it provides a simple RPC(remote procedure call) layer on top define function prototypes and simple data structures.
      MIG interfaces are declared in .def files. These use a simple Interface Definition Language which can define function prototypes and simple data structures. The MIG tool comples the .defs into C code which implements all the required argument serialization/deserialization.

###IOKit
The framework used for writing device drivers on OS X. in C++.
All interactions with IOKit bgin with the IOKit master port. This is another special mach port which allows access to the IOkit registry. devices.defs is the relevant MIG definition file. [link]
The IOKit registry allows userspace programs to findout about available hardware. furthermore, device drivers can expose an interface to userspace by implementing a UserClient.
The main way which userspace actually interacts with an IOKit driver’s UserClient is via the io_connect_method MIG RPC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type io_scalar_inband64_t = array[*:16] of uint64_t;
type io_struct_inband_t = array[*:4096] of char;
routine io_connect_method(
connection : io_connect_t;
in selector : uint32_t;
in scalar_input : io_scalar_inband64_t;
in inband_input : io_struct_inband_t;
in ool_input : mach_vm_address_t;
in ool_input_size : mach_vm_size_t;
out inband_output : io_struct_inband_t, CountInOut;
out scalar_output : io_scalar_inband64_t, CountInOut;
in ool_output : mach_vm_address_t;
inout ool_output_size : mach_vm_size_t
);

This method is wrapped by the IOKitUser library function IOConnectCallMethod.
The kernel implementation of this MIG API is in IOUserClient.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
kern_return_t is_io_connect_method
(
io_connect_t connection,
uint32_t selector,
io_scalar_inband64_t scalar_input,
mach_msg_type_number_t scalar_inputCnt,
io_struct_inband_t inband_input,
mach_msg_type_number_t inband_inputCnt,
mach_vm_address_t ool_input,
mach_vm_size_t ool_input_size,
io_struct_inband_t inband_output,
mach_msg_type_number_t *inband_outputCnt,
io_scalar_inband64_t scalar_output,
mach_msg_type_number_t *scalar_outputCnt,
mach_vm_address_t ool_output,
mach_vm_size_t *ool_output_size
)
{
CHECK( IOUserClient, connection, client );
IOExternalMethodArguments args;
...
args.selector = selector;
args.scalarInput = scalar_input;
args.scalarInputCount = scalar_inputCnt;
args.structureInput = inband_input;
args.structureInputSize = inband_inputCnt;
...
args.scalarOutput = scalar_output;
args.scalarOutputCount = *scalar_outputCnt;
args.structureOutput = inband_output;
args.structureOutputSize = *inband_outputCnt;
...
ret = client->**externalMethod**( selector, &args );

Fill in an IOExternalMethodArguments structure from the arguments passed to the MIG RPC and then calls the ::externalMethod of the IOUserClient(driver’s interface).
Next is about the structure of the driver’s IOUserClient subclass. If the driver overrides externalMethod then this calls straight into driver code. Typically the selector argument to IOConnectCallMethod would be used to determine what function to call, but if the subclass overrides externalMethod it’s free to implement whatever method dispatch mechanism it wants. However if the driver subclass doesn’t override externalMethod the IOUserClient implementation of it will call getTargetAndMethodForIdex passing the selector argument - this is the method which most IOUserClient subclasses override - it returns a pointer to an IOExternalMethod structure:

1
2
3
4
5
6
7
struct IOExternalMethod {
IOService * object;
IOMethod func;
IOOptionBits flags;
IOByteCount count0;
IOByteCount count1;
};

Most drivers have a simple implementation of getTargetAndMethodForType which uses the selector argument to index an array of IOExternalMethod Structures. This structure contains a pointer to a method to be invoked (and since this is C++ this isn’t actually a function pointer but a pointer-to-member-method which means things can get very fun when you get to control it! See the bug report for CVE-2014-1379 in the Project Zero bugtracker for an example of this.)
如果IOExternlMethod被重写了,那么就会直接call被重写的function,否则就要call getTargetAndMethodForIndex,它会返回IOExternalMmethod structure.
The flags is used to define what mixture of input and output types the ExternalMethod supports and the count0 and count1 fields define the number or size in bytes of the input and output arguments. There are various shim functions which make sure that func is called with the correct prototype depending on the declared number and type of arguments.
At this point we know that when we call IOConnectCallMethod what really happens is that C code auto-generated by MIG serializes all the arguments into a data buffer which is wrapped in a mach message which is sent to a mach port we received received from the IOKit registry which we knew how to talk to because every process has a special device port. That message gets copied into the kernel where more MIG generated C code deserializes it and calls is_io_connect_method which calls the driver’s externalMethod virtual method.
说真的,这个过程真的把我绕晕了。关键点是MIG会自动生成一些代码。
SWEP&SMAP
SMEP and SMAP are two CPU features designed to make exploitation of this type of bug trickier.
SWEP Supervisor Mode Execute Prevention which means that when the processor is executing kernel code the CPU will fault if it tries to execute code on pages belonging to userspace. This prevents us from simply mapping an executable kernel shellcode payload at a known address in userspace and getting the kernel to jump to it.
The generic defeat for this mitigation is code-reuse (ROP). Rather than diverting execution directly to shellcode in userspace instead we have to divert it to existing executable code in the kernel. By “pivoting” the stack pointer to controlled data we can easily chain together multiple code chunks and either turn off SMEP or execute an entire payload just in ROP.

SMAP Supervisor Mode Access Prevention. As the name suggests this prevents kernel code from even reading user pages directly. This would mean we’d have to be able to get controlled data at a known location in kernel space for the fake IOKit object and the ROP stack since we wouldn’t be able to dereference userspace addresses, even to read them.