Exploit for Apple iOS version 12.1.3
Following our previous blog post “Analysis and Reproduction of iOS/OSX Vulnerability: CVE-2019-7286” we discussed the details of CVE-2019-7286 vulnerability – a double-free vulnerability that was patched in the previous release of iOS and was actively exploited in the wild. There is no public information about this vulnerability.
ZecOps Research Team thought that only reproducing this vulnerability without learning how an attacker could have achieved elevated privileges through a vulnerability that has a small time window, would be less educational. In this blog post we will demonstrate one of the ways of how attackers could have exploited this vulnerability.
- CVE-2019-7286 was exploited in the wild and fixed on the latest iOS (12.1.4)
- The vulnerability seems to be of critical severity and could have been used potentially also to maintain persistence after reboots
- ZecOps Research Team analyzed and reproduced the vulnerability.
- New: ZecOps is releasing a new POC that can demonstrate full Program Counter (PC) control (below).
- The vulnerability could be used to escalate privileges to root as part of a chain for jailbreak on iOS 12.1.3.
Since both of the frees are located in a single XPC request, it’s not possible to control the data between the two xpc_release calls. Therefore, we need another XPC request to create an additional thread to fill the freed memory, as shown below.
Since the first xpc_release is at the end of the function and shortly after the second free will occur (double-free), means that the time window to exploit this vulnerability is small.
From Double-Free to Use-After-Free
The time window between the two xpc_release (xpc_release frees a xpc_object) calls is short. The pseudo code below shows that the double freed object is an xpc_dictionary which resides inside an xpc_array.
current_element = xpc_buffer[counter];
if (xpc_get_type(current_element) != &_xpc_type_null )
Since it is possible to fully control the contents of XPC requests, creating an xpc_array that is long enough creates a sufficient time window to fill the freed memory.
It is necessary to decide which object should fill the freed memory.
We need to find an object which meets the following criteria:
- The first 8 bytes can be controlled, which allows to control the ISA pointer
- The size of the object should be 0xc0 – the same as the freed xpc_dictionary_t, which is more likely to fill the freed memory
- It should be possible to control the memory allocation, so we can increase the fill rate
Let’s look at OS_xpc_string first. When deserializing OS_xpc_string, the function xpc_string_deserialize calls xpc_try_strdup which is a wrapper of strdup, as shown below.
By controlling the length of the string, we are able to control the size of the allocation. Adding multiple OS_xpc_string objects into deserialized dictionary or array may also increase the filling rate.
Now the 0xc0 length string gives us more than 60% success rate for filling the freed object.
Once we have obtained the full control of the freed object let’s proceed to the next stage.
The first 8 bytes of an Object-C object is the ISA pointer. Pointing the ISA pointer to a controlled memory space gives us control of the Object-C method call ( see details here: Phrack Article).
Due to Address space layout randomization (ASLR) it is impossible to identify any address in the cfprefsd process – a traditional way to bypass ASLR is heap spray which was introduced by Ian Beer in the XPC heap spray technique in his talk.
The sprayed data resides in the VM_ALLOCATE region, and can be reliably found at 0x180202000. We need to shift our data a bit, using 0x180202020 instead of 0x180202000, since we are using strings to fill the freed object, which is null-terminated. If we use 0x180202000, the first null byte which is the first byte of the string will terminate the string.
Now we can finally control the PC:
Increasing Exploit Success Rate
With PC-control, the success rate drops from 60%+ to 5%-. In order to identify additional ways to increase the success rate of the exploit, let’s review the following aspects:
- A 0xc0 length string is used to fill the freed object
- The string is null-terminated
- The ISA pointer is the first 8 bytes of a Object-C object
When we change the first 8 byte of the string into 0x180202020, in a 64bit operating system, the pointer is actually 0x0000000180202020, which means if we set the 1st 8 byte of the string into an address we want, the zeros will terminate the string at the 5th byte. The string is likely allocated somewhere else since OSX uses size-based free list to speed up allocations.
Unlimited Attempts till Return Oriented Programming (ROP)
In this exploit example, we demonstrated that it is possible to hijack the code execution flow. We have filled the freed object with a decent success rate. The null-terminated string decreases exploit’s success rate significantly (from 60% to 5%). In order to get a reliable PC-control, another object is required which won’t be null-terminated to fill the gap. Please note that cfprefsd is registered as Launch Daemon and Agent. After a crash, it will be launched again for new XPC requests, which allows an attacker to target this daemon as needed until escalation of privileges is achieved.
After controlling PC, we can construct a payload to do Return Oriented Programming to execute arbitrary commands by calling system() or get the task port of the cfprefsd as Brandon did.
The exploit code is intended for educational and defensive purposes only. Use at your own risk.