Posted by Samuel Groß, Project Zero
In this post, we will take a look at the WebKit exploits used to gain an initial foothold onto the iOS device and stage the privilege escalation exploits. All exploits here achieve shellcode execution inside the sandboxed renderer process (WebContent) on iOS. Although Chrome on iOS would have also been vulnerable to these initial browser exploits, they were only used by the attacker to target Safari and iPhones.
After some general discussion, this post first provides a short walkthrough of each of the exploited WebKit bugs and how the attackers construct a memory read/write primitive from them, followed by an overview of the techniques used to gain shellcode execution and how they bypassed existing JIT code injection mitigations, namely the “bulletproof JIT”.
It is worth noting that none of the exploits bypassed the new, PAC-based JIT hardenings that are enabled on A12 devices. The exploit writeups are sorted by the most recent iOS version the exploit supports as indicated by a version check in the exploit code itself. If that version check was missing from the exploit, the supported version range was guessed based on the date of the fix and the previous exploits.
The renderer exploits follow common practice and first gain memory read/write capabilities, then inject shellcode into the JIT region to gain native code execution. In general it seems that every time a new bug was necessary/available, the new bug was exploited for read/write and then plugged into the existing exploit framework. The exploits for the different bugs also appear to generally use common exploit techniques, e.g. by first creating the addrof and fakeobj primitives, then faking JS objects to achieve read/write.
For many of the exploits it is unclear whether they were originally exploited as 0day or as 1day after a fix had already shipped. It is also unknown how the attackers obtained knowledge of the vulnerabilities in the first place. Generally they could have discovered the vulnerabilities themselves or used public exploits released after a fix had shipped. Furthermore, at least for WebKit, it is often possible to extract details of a vulnerability from the public source code repository before the fix has been shipped to users. CVE-2019-8518 can be used to highlight this problem (as can many other recent vulnerabilities). The vulnerability was publicly fixed in WebKit HEAD on Feb 9 2019 with commit 4a23c92e6883. This commit contains a testcase that triggers the issue and causes an out-of-bounds access into a JSArray - a scenario that is usually easy to exploit. However, the fix only shipped to users with the release of iOS 12.2 on March 25 2019, roughly one and a half months after details about the vulnerability were public. An attacker in possession of a working exploit for an older WebKit vulnerability would likely only need a few days to replace the underlying vulnerability and thus gain the capability to exploit up-to-date devices without the need to find new vulnerabilities themselves. It is likely that this happened for at least some of the following exploits.
For comparison, here is how other browser vendors deal with this “patch-gap” or vulnerability window problem:
After some general discussion, this post first provides a short walkthrough of each of the exploited WebKit bugs and how the attackers construct a memory read/write primitive from them, followed by an overview of the techniques used to gain shellcode execution and how they bypassed existing JIT code injection mitigations, namely the “bulletproof JIT”.
It is worth noting that none of the exploits bypassed the new, PAC-based JIT hardenings that are enabled on A12 devices. The exploit writeups are sorted by the most recent iOS version the exploit supports as indicated by a version check in the exploit code itself. If that version check was missing from the exploit, the supported version range was guessed based on the date of the fix and the previous exploits.
The renderer exploits follow common practice and first gain memory read/write capabilities, then inject shellcode into the JIT region to gain native code execution. In general it seems that every time a new bug was necessary/available, the new bug was exploited for read/write and then plugged into the existing exploit framework. The exploits for the different bugs also appear to generally use common exploit techniques, e.g. by first creating the addrof and fakeobj primitives, then faking JS objects to achieve read/write.
For many of the exploits it is unclear whether they were originally exploited as 0day or as 1day after a fix had already shipped. It is also unknown how the attackers obtained knowledge of the vulnerabilities in the first place. Generally they could have discovered the vulnerabilities themselves or used public exploits released after a fix had shipped. Furthermore, at least for WebKit, it is often possible to extract details of a vulnerability from the public source code repository before the fix has been shipped to users. CVE-2019-8518 can be used to highlight this problem (as can many other recent vulnerabilities). The vulnerability was publicly fixed in WebKit HEAD on Feb 9 2019 with commit 4a23c92e6883. This commit contains a testcase that triggers the issue and causes an out-of-bounds access into a JSArray - a scenario that is usually easy to exploit. However, the fix only shipped to users with the release of iOS 12.2 on March 25 2019, roughly one and a half months after details about the vulnerability were public. An attacker in possession of a working exploit for an older WebKit vulnerability would likely only need a few days to replace the underlying vulnerability and thus gain the capability to exploit up-to-date devices without the need to find new vulnerabilities themselves. It is likely that this happened for at least some of the following exploits.
For comparison, here is how other browser vendors deal with this “patch-gap” or vulnerability window problem:
- Google has this same problem with Chromium (e.g. commit 52a9e67a477b fixing CVE-2018-17463 and including a PoC trigger). However, it appears that some recent bugfixes no longer include the JavaScript test cases commits. For example the following two fixes for vulnerabilities reported by our team member Sergey Glazunov: aa00ee22f8f7 (for issue 1784) and 4edcc8605461 (for issue 1793). In the latter case, only a C++ test was added that tested the new behaviour without indication of how the vulnerable code could be reached.
- Microsoft keeps security fixes in the open source Chakra engine private until the fixes have been shipped to users. The security fixes are then released and marked as such with a CVE identifier. See commit 7f0d390ad77d for an example of this. However, it should be noted that Chakra will soon be replaced by V8 (Chromium’s JavaScript engine) in Edge.
- Mozilla appears to hold back security fixes from the public repository until somewhat close to the next release. Furthermore, the commits usually do not include the JavaScript testcases used to trigger the vulnerability.
However, it is worth noting that even if no JavaScript testcase is attached to the commit, it is often still possible to reconstruct a trigger (and ultimately an exploit) for the vulnerability from the code changes and/or commit message with moderate effort.
Exploit 1: iOS 10.0 until 10.3.2
This exploit targets CVE-2017-2505 which was originally reported by lokihardt as Project Zero issue 1137 and fixed in WebKit HEAD with commit 4a23c92e6883 on Mar 11th 2017. The fix was then shipped to users with the release of iOS 10.3.2 on May 15th 2017, over two months later.
Of interest, the exploit trigger is almost exactly the same as in the bug report and the regression test file in the WebKit repository. This can be seen in the following two images, the left one showing the testcase published in the WebKit code repository as part of the bugfix and the right showing the part of the in-the-wild exploit code that triggered the bug.
The bug causes an out-of-bounds write to the JSC heap with controlled data. The attackers exploit this by corrupting the first QWord of a controlled JSObject, changing its Structure ID (which associates runtime type information with a JSCell) to make it appear as a Uint32Array instead. This way, they essentially create a fake TypedArray which directly allows them to construct a memory read/write primitive.Of interest, the exploit trigger is almost exactly the same as in the bug report and the regression test file in the WebKit repository. This can be seen in the following two images, the left one showing the testcase published in the WebKit code repository as part of the bugfix and the right showing the part of the in-the-wild exploit code that triggered the bug.
No comments:
Post a Comment