|Vulnerability in SecureROM|
|Software||Devices running iOS, tvOS, watchOS, bridgeOS, Haywire, etc.|
|Vulnerable versions||? - 3401.0.0.1.16|
|Fixed in version||3822.214.171.124.7|
|Disclosed||27 September 2019|
|Discovered by||axi0mX (tweet)|
The checkm8 exploit is a bootrom exploit with a CVE ID of CVE-2019-8900 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 a both a use-after-free and a memory leak in the USB DFU stack. The main use-after-free is actually unpatched in T8020, T8027 or T8030, but cannot be exploited without the second memory leak, which is 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.
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[edit source]
The use-after-free vulnerability exists where the data length global variable is updated with the length of the data to be received. If you send a USB transfer that violates the spec (SecureROM won't expect this) and send nothing, and then use a USB reset to trigger image parsing, SecureROM will fail to parse the image. As a result, 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.
Memory Leak Vulnerability[edit source]
When a request is fulfilled or the USB stack is shut down, a zero-length packet is sent. However, in these SoCs the zero-length packet is never sent and ends up being leaked. This leak can be triggered conditionally as only packets that match specific conditions will trigger this leak - for instance, requests that are 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 and A9 - where a DFU abort bug is abused to achieve direct code execution without the need of ROP or JOP.
Heap feng-shui techniques (also known as heap grooming) is used to mould the heap into a condition that is beneficial for exploitation by making it easier to overflow the necessary areas of memory. After this, an incomplete OUT request is created for uploading the image. As a result, the global variables are initialised and the address of the IO buffer is written to one of them. A DFU_CLR_STATUS request is 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. This means that the callback function now skips code that could cause the exploit to fail.
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.
Known exploitation issues[edit source]
- 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.