Escaping Sandbox Using Callbacks on iOS 11.4
The Alibaba tech team’s OS jailbreak internals
(Article by Zheng Min郑旻 and Bai Xiaolong白小龙 from the Alibaba Security Lab)
Apple’s sandbox was introduced as “SeatBelt” in macOS 10.5 which provided the first full-fledged implementation of the MACF policy. After a successful trial on macOS, Apple applied sandbox mechanism to iOS 6. In its implementation, the policy hooked dozens of operations. The number of hooks has been growing steadily when new system calls or newly discovered threats appeared. The following table shows the hook count of sandbox in different XNU versions:
In the beginning, Apple’s sandbox used a black list approach which means Apple originally concentrated on the known dangerous APIs and blocked them, allowing all others by default. However, with the evolution of Apple’s sandbox, it applies a white list approach that denies all APIs and only allows secure ones that Apple trusts.
In macOS, profiles are visible and stored in /System/Library/Sandbox/Profiles. In iOS, the profiles were hard-compiled into /usr/libexec/sandboxd. It’s hard for us to decode the sandbox profiles, but we can traverse all Mach services to get the mach-lookup list according to the return value (e.g., through JL’s sbtool).
In order to find vulnerabilities, we need to disassemble and analyze the binaries which contain the handler functions of related Mach services. Luckily, /System/Library/LaunchDaemons contains the configuration plist of most Mach services. In the plist files, “ProgramArguments” shows the path of the binary and “MachServices” shows the related Mach services. Therefore, by combining the LaunchDaemons plist files with mach-lookup list, we can get all the Mach services and related binaries which can be accessed inside the sandbox.
iOS IPC: Mach, XPC and NSXPC
There are a rich set of IPC mechanisms (e.g., URL schemes, Mach, pipes) on iOS and most of them are available to third-party apps. In this paper, we focus on Mach IPC, which provides a message-oriented and capability-based IPC facility. Mach IPC represents an evolution of similar approaches used by its precursors, namely, Accent and RIG. The low-level IPC implementation of Mach leverages the VM subsystem to efficiently transfer large amounts of data with the help of copy-on-write optimizations. In addition, Mach messages contain typed data, which can include port rights and references to large regions of memory.
Based on Mach message, Apple developed XPC which is a lightweight mechanism for basic inter process communication integrated with GCD and launchd. Compared with raw Mach message, XPC is safer and easier to use. But, the cost of XPC service maintaining is very heavy. NSXPC message is built on top of XPC message which allows the abstraction of XPC connections and remote objects. Through Mach message, XPC message, and NSXPC message, sandboxed app can communicate with unsandboxed Mach services, XPC services and NSXPC services. Therefore, if the services don’t handle the message in expected ways, they may be corrupted by sandboxed apps.
From Old Bugs to 0-day Bugs
In iOS 11.2.5, Apple fixed a session hijack vulnerability in bluetoothd (CVE-2018–4087 by @raniXCH). There are 132 functions in the “com.apple.server.bluetooth” Mach service of bluetoothd. And the vulnerability existed in the BTLocalDeviceAddCallbacks() function. A sandboxed app can use BTSessionAttach to create a session_token for bluetoothd and then use BTLocalDeviceAddCallbacks() to register a callback for event notification.
However, Bluetoothd only uses the session token to identify the process which means we can use a sandboxed app to hijack a communication between bluetoothd and unsandboxed processes through the session token, then control the PC pointer of unsandboxed processes.
In addition, the session token is too easy to be brute forced because it only has 0x10000 (0x0000 -0xFFFF) possible values. In iOS 11.2.5, Apple fixed this problem by adding a user_id (=arc4random()) to each session. Only the process knows the user_id and bluetoothd will check the map of ses_token with user_id.
However, after analyzing other functions, we found two new zero-day bugs. The first bug exists in BTAccessoryManagerAddCallbacks(). Same as BTLocalDeviceAddCallbacks(), BTAccessoryManagerAddCallbacks() will register a callback for BTAccessoryManger but the attacker doesn’t need to provide a user_id. However, the callback event can be triggered only when the iOS device connects to a new device. It means we need to click the bluetooth device in the setting manually in order to trigger the callback event.
Luckily, we found another bug existed in the discovery agent of bluetooth. A sandboxed app can use BTDiscoveryAgentCreate() to create a discovery agent callback for other processes and then use BTDiscoveryAgentStartScan() to trigger the callback event without manual click.
Update: these two “zero-day”bugs were reported to Apple on June 7, Apple fixed them in iOS 11.4.1 as well as iOS 12 beta with CVE-2018–4330 and CVE-2018–4327. Please update your iOS to the latest iOS 11.4.1 to defend against potential attacks.
From PC control to ROP
The goal of our exploit is to control not only the PC pointer but the whole process as well. Therefore, we need to create a ROP chain and do a heap spray for the target process. In this case, we use MACH_MSGH_BITS_COMPLEX Mach msg with MACH_MSG_OOL_DESCRIPTOR memory to do the heap spray. If we send this kind of msg to the target process and don’t receive the msg, the ROP chain will stay in the target’s memory space persistently. In addition, we could use a magic address (0x105400000) to set the callback address for the PC to jump.
In order to control the program flow easily, we need to control the stack register. Therefore, we need to find a gadget to do the stack pivot.
A great stack pivot gadget can be found at libsystem_platform.dylib. By using this gadget, we can control the SP register through the X0 register. After that, we could set X30 register and then use ``ret’’ gadget to execute arbitrary ROP chains.
From ROP to task port
In iOS/macOS, a port provides an endpoint for IPC. Messages can be sent to a port or received from it. In addition, ports can contain rights and port rights can be passed in messages. The most important port for one process is its task port which can be obtained by mach_task_self(). One can control the memory and all registers of a process through its task port. For example, mach_vm_allocate(target_task_port, &remote_addr, remote_size, 1) can be used to allocate memory in a remote process and mach_vm_write(target_task_port, remote_address, local_address, length) can be used to copy data into a remote process. Therefore, if we can get one process’s task port, we can easily control the whole process through Mach msg.
Some tricks learned from Mach_portal: a user mode exploit technique is to execute ROP in the remote process and send the remote process’s task port back to the attack app. Firstly, We use mach_port_allocate() to allocate 0x1000 ports in the attack app and then use mach_port_insert_right() to insert a send right to these ports. After that, we can send these ports to the remote process through OOL Mach messages with MACH_MSG_PORT_DESCRIPTOR type. The next step is to do a heap spray in order to send the ROP chain to the remote process and then use the bluetoothd bug to control the PC. In order to send the task port back to our attack app through the ROP chain, we need to know the port number of our attack app. However we cannot use launchd to help us. Luckily, the port number can be guessed by brute force (0x103+0x100*N). That’s why we send 0x1000 ports to the remote process (in order to increase the successful rate) and use the ROP chain to call mach_msg() 0x100 times to guess the port number.
After getting the task port of the unsandboxed process, we can fully control of it. However, iOS 11 (not in macOS10.13) extended the limit to the use of all task ports for sandboxed app processes, we cannot get the task port using previous method:
Even we cannot get the task port, we can still use ROP. A generic primitive for function calls with arbitrary parameters can be found in CoreFoundation:
By using these ROP gadgets, we could control unsandboxed system services and further attack the kernel through more attack surfaces.
1. MacOS and *OS Internals http://newosxbook.com/
2. Pangu 9 Internals https://www.blackhat.com/docs/us-16/materials/us-16-Wang-Pangu-9-Internals.pdf
3. triple_fetch https://bugs.chromium.org/p/project-zero/issues/detail?id=1247
These two “zero-day”bugs were reported to Apple on June 7, Apple fixed them in iOS 11.4.1 as well as iOS 12 beta with CVE-2018–4330 and CVE-2018–4327. Please update your iOS to the latest iOS 11.4.1 to defend against potential attacks.