TFP0 POC on PAC-Enabled iOS Devices <= 12.4.2

This blog provides an overview of an exploitation technique to bypass Pointer Authentication Code (PAC) which was introduced on all iOS devices since A12. This blog will focus on CVE-2019-8797, CVE-2019-8795 and CVE-2019-8794. The remainder of this report provides additional details about PAC bypass on iOS <= 12.4.2.

November 25 2019 by

Jamf Threat Labs

A person arranges n iPad and two iPhones managed by Jamf.

For more information regarding the vulnerability that is possible to trigger and exploit on iOS devices with no PAC capability, please refer to this SSD Secure Disclosure piece. We would like to thank 08Tc3wBB for this submission.

Userspace Exploit

The published exploit describes how to achieve sandbox escape by exploiting CVE-2019-8797 of MIDIServer.

Following is the disassembly code from MIDIIORingBufferWriter::EmptySecondaryQueue on non-PAC devices. The published exploit is able to hijack the PC register by controlling the value of x8 in the highlighted line by exploiting CVE-2019-8797.

However, on the devices with PAC, the instruction ldraa loads pointer with pointer authentication, which means that we need to pass an address with proper authentication to x8.

Trying ROP without stripping the PAC pointer, triggers the following crash on the target process:

This concept of PAC bypassing is derived from Ian Beer’s post. Let’s take a look at the document to see how ldraa works:

Load Register, with pointer authentication. This instruction authenticates an address from a base register using a modifier of zero and the specified key, adds an immediate offset to the authenticated address, and loads a 64-bit doubleword from memory at this resulting address into a register.

Key A is used for LDRAA, and key B is used for LDRAB.

After gaining initial PC Control, we still need to control other registers for arbitrary code execution. At this point, x0 contains a pointer that points to a memory we control, which is also A-key encrypted.

When the targeted pointer is encrypted, normal instructions like ldr, ldp will trigger a crash.

Now, we need to strip code authentication for those registers. Following gadgets are used to strip the registers we need:

After controlling PC, X0 and X2, we are able to call xpc_array_apply_f with x0 pointing to a crafted fake xpc array. Afterwards, the function pointer is called each time in a loop with a controllable x2 register:

By pointing the function pointer to IODispatchCalloutFromMessage and making each element of the fake xpc_array to point to a fake mach message matching the format expected by IODispatchCalloutFromMessage, it is possible to chain together an arbitrary number of basic function calls.

There are limitations compared to regular ROP, however this is sufficient to achieve the goal including opening up a kernel surface to proceed to kernel exploit.

Kernel Exploit

Kernel PAC does not affect the information disclosure bug, hence KASLR can be bypassed.

In the published exploit, the PC is controlled by hijacking vtable in the line with a comment (a). Since we are not able to use JOP due to PAC, we need another primitive for PAC bypass.

Let’s take a look at the AppleAVE2Driver::DeleteMemoryInfo function from another angle, emphasizing that we have full control of *memInfo.

Following is the code that is called by destroy_iosurfaceinfo_buf. We can avoid triggering code execution by setting the value of some of the offsets (e.g. 0x28, 0x30 and 0x20 etc.) to 0 in order to control the pointer passed to operator delete (see comment (b)). In this way, we turn this vulnerability into another powerful primitive – Arbitrary Memory Release.

With this primitive, we can now use the strategy introduced in BlackHat USA 2019 by TieLei Wang and Hao Xu.

1. Spray OSdata that has the size of a page (0x4000), after spraying a large volume of OSdata, it’s not hard to predict the address of one of the OSdata. The predicted_address allows us to construct the OSdata which contains the fake ipc_port_struct and task_struct for leaking addresses of kernel_map and ipc_space_kernel in the following steps.

Noteworthy, OSata uses kmem_alloc instead of kmem_alloc_kobject while request size is equal or greater than page_size(0x4000), since kmem_alloc allocates data outside of kernel_object. we need to shrink spray_data_len to 0x3FFF.


2. Trigger vulnerability to release the OSdata at predicted_address.


3. Send ool_ports descriptor of the same size as the free’d OSdata to ourselves, to fill up that hole.


4. Release filled data by using iosurface, so that the memory that holds ool_ports is released.

ool_ports   |                      +------+------+   |  OSDATA  |  OSDATA  |   FREE   |  OSDATA  |  OSDATA  |

5. Re-spray the OSdata with fake ipc objects to get control of ipc port structure.

Please note that at this point we still couldn’t build a fake tfp0 due to not knowing the addresses of kernel_map and ipc_space_kernel.

| ool_ports   | -------+ |  OSDATA  |  OSDATA  |FAKE PORTS|  OSDATA  |  OSDATA  |

6. Receive these ports which are translated from in-kernel ipc port structure that we have controlled in the previous step. Next, the fake ipc_port_struct and task_struct that was sprayed in step 1 is used for kernel info leak in order to read values from kernel memory with these fake ports, via pid_for_task.

Now, we have all the addresses required to build fake TFP0 ports.

7. Release and spray with the OSdata which contains fake TFP0 port while the ool_ports pointer still pointing to a fake ipc_port_struct. TFP0 achieved!

ool_ports   |                      +------+------+                             |FAKE TFP0 |FAKE TFP0 |FAKE TFP0 |FAKE TFP0 |FAKE TFP0

Jamf offers advanced mobile detection and response capabilities.

Discover Jamf Executive Threat Protection today.

Subscribe to the Jamf Blog

Have market trends, Apple updates and Jamf news delivered directly to your inbox.

To learn more about how we collect, use, disclose, transfer, and store your information, please visit our Privacy Policy.