Checkm8 Exploit

The checkm8 exploit is a bootrom exploit with a CVE ID of used to run unsigned code on iOS, iPadOS, tvOS, watchOS, bridgeOS, audioOS, and Haywire devices with processors between an A5 and an A11, a S1P and a S3, a S5L8747, and a T2 (and thereby jailbreak it). Jailbreaks based on checkm8 are semi-tethered jailbreaks as the exploit works by taking advantage of vulnerabilities in the USB DFU stack. The main use-after-free is actually unpatched in T8020, T8027 or T8030, but cannot be exploited without a memory leak, of which the one used in checkm8 was made unreachable in T8020 and above.

ipwndfu, Fugu, gaster and checkm8-a5 are currently the main tools capable of exploiting checkm8.

checkra1n and palera1n are currently the main tools capable of performing a jailbreak with the checkm8 exploit.

Background
When DFU initialises USB, it registers an interface to handle all transfers and allocates a global input/output buffer for these transfers. When data is sent to DFU, the setup packet is handled by the main USB code, which hands off to the DFU interface. The interface code ensures that the wLength parameter is shorter than the buffer allocated before USB initialisation. If this is true, it updates a pointer that would be used for the output buffer to that of the global IO buffer.

The main code then updates a global variable with the length of the data to be sent and then prepares for the data phase. Data that is received is written into the IO buffer and a second global variable keeps track of how much of the data has been received. Once all data is received, DFU copies the contents of the buffer into the area where the image would later be booted. The USB code then resets all the global variables and prepares for the next transfer.

If DFU exits, the IO buffer is freed. If parsing fails, SecureROM re-enters DFU.

Use-after-Free Vulnerability
The use-after-free vulnerability exists within the implementation of the data phase in a USB transfer. First of all, you must trigger the beginning of the data phase within the USB stack. Then, if you send a USB transfer that violates the standards of the USB specification (SecureROM won't expect this) and send nothing. After this, by triggering the re-entry of DFU (through a USB reset or by triggering image parsing - which will inevitably fail), SecureROM re-enters DFU, and the IO buffer is freed. Yet, because the process was aborted early, the USB code never reset the global variables - most importantly, the one that holds a pointer to the now-freed IO buffer and that will be used as the main USB transfer buffer.

Memory Leak Vulnerability
When a request is fulfilled by the USB stack, a zero-length packet is sent to signal the end of a transfer. However, in certain checkm8 SoCs (A9X, A10, A10X and A11), there is a vulnerability that allows for these request objects to leak and remain allocated through a DFU shut-down. By stalling the device and then sending lots of requests, each request will have a zero-length packet request allocated on the heap. However, by triggering the shut-down of DFU before these requests have a chance to be processed, the zero-length packets are never properly sent and de-allocated, and end up being leaked. This leak can be triggered conditionally, as only packets that match specific conditions will cause a zero-length packet to be queued - for instance, requests with a length that is not a multiple of 64 bytes won’t trigger the leak. This heap leak can be used in order to carefully craft the state of the heap and end up with an exactly sized hole, which will be the preferred place for malloc to put the buffer on the next allocation.

This leak is not needed on A8, A8X and A9 devices - where a DFU abort bug is abused to achieve direct code execution without the need of ROP or JOP. On A7, the leak is triggered for all requests (instead of those which meet the right conditions), so the strategy used is to almost fill up the heap until there is just enough space left that there is a preferred hole for the buffer.

Exploitation
Heap feng-shui techniques (also known as heap grooming) is used to ensure that the IO buffer for the next DFU iteration (after triggering the use-after-free) is not allocated on top of the freed one. After this, an incomplete data phase transfer takes place in order to set the global variables and enter the checkm8 state. A DFU_CLR_STATUS request is then sent, which results in a DFU reset and a new cycle commencing. The IO buffer is now freed and the global state remains as it was before with the variables intact.

A usb_device_io_request object is then overflowed with an overwrite and has replaced the pointer to a callback function to be executed once the request is done and a pointer to the next object of the same type. By doing so, it prevents the device from trying to free the request (which will cause a panic because the heap metadata is damaged), and then will direct control flow to the payload.

After this, the payload is sent over USB and then a USB reset is sent. This reset triggers a loop of cancelling incomplete requests in the queue. The rest of queue has been replaced by the exploit - meaning we have control of the callback chain, and control is then transferred to our shellcode once the device executes the callback function of our overflown request.

Known exploitation issues

 * Exploitation on A5 - checkm8-a5 must be run on an Arduino due to a difference in how this SoC handles USB packets. Because of this, exploitation is not possible with a regular computer because full control over USB requests sent is needed - including the ones which would be sent by default when the device is connected to a computer.
 * Exploitation on Apple Silicon Macs - an issue in the USB stack of these Macs causes certain device to not be exploited by checkm8 over a USB-C port. checkra1n and gaster overcome this issue - but other tools such as ipwndfu and Fugu won't work. More info can be found at this blog post from checkra1n.