CVE-2020-1380: Internet Explorer JScript9 UAF

This page has been moved to our new site. Please click here to go to the new location.
Maddie Stone, Samuel Groß

Disclosure or Patch Date: 11 August 2020
Product: Microsoft Internet Explorer
Affected Versions: For Windows 10 2004, KB4565503 and previous
First Patched Version: For Windows 10 2004, KB4566782
Issue/Bug Report: N/A 
Patch CL: N/A
Bug-Introducing CL: N/A
Access to the exploit sample? No 
Reporter(s): Boris Larin (Oct0xor) of Kaspersky Lab (Thanks to Kaspersky Lab for sharing their detailed analysis!)

Bug Class: Use-after-free
Vulnerability Details: This vulnerability is a standard UaF due to missing checks from JIT compiler optimization in the JScript9 engine. When setting an element in a Float32Array to an object, the JIT fails to check if the underlying ArrayBuffer for the Float32Array still exists. The ArrayBuffer can be detached during an object’s custom callback valueOf, which is invoked when converting the object to float. The result of valueOf is stored in the freed ArrayBuffer, creating a use-after-free vulnerability.

Is the exploit method known? Yes
Exploit method: 
This section is wholly based on Kaspersky’s blog post since I did not have a copy of the exploit.

Each step of the exploitation method is well known and commonly used. 
To force the JIT to compile the function, the function is called many times with only an integer being stored to the Float32Array. This is a common way to force the JIT to compile. To free the underlying ArrayBuffer, the exploit uses the web workers postMessage function, which is a well known and often used technique to trigger UAFs on typed arrays. To trigger the garbage collection, the exploit creates a “Sleep” method, which causes GC to occur based on exhausting its timeout. The exploit re-allocates the freed memory with a LargeHeapBlock. The exploit wants a memory layout where the call to store an object at an index the Float32Array will overwrite the Allocated Block Count (+0x14) in the LargeHeapBlock with 0. The exploit then does more allocating and freeing of arrays to ultimately end up with two JavascriptNativeIntArray objects whose ‘head’ members point to the same address. The OOB r/w afford by the JavascriptNativeIntArrays is then used to create new  DataView objects to get arbitrary read/write primitives. These are commonly used techniques for getting the r/w primitives in Internet Explorer.

Once the exploit has arbitrary r/w, it needs to get code execution. The exploit finds its stack address and use a ROP chain to execute its own shellcode. It gets its stack address using the function LinktoBeginning<ThreadContext> and traversing the linked list of ThreadContext objects for its own. To find the address of VirtualProtect, in order to set its shellcode buffer to executable, the exploit calculates the base address for the jscript9.dll module by reading the vftable pointer and then gets other modules’ base address from the Import Directory Table in the jscript9 PE header. The shellcode loads the elevation of privilege exploit, CVE-2020-0986.

How do you think you would have found this bug?
It seems unlikely that the bug was uncovered purely through reverse-engineering of the JIT compiler. That leaves three options:

1. Fuzzing, e.g. with a generative fuzzer or something like Fuzzilli.
Generic JS fuzzers will likely have a bit of a hard time generating trigger code for these callback/reentrancy/side-effect-mismodelling related issues (possibly because they require specifically crafted dataflow that a coverage-guided fuzzer isn't rewarded for). It's definitely plausible that a fuzzer like Fuzzilli would find this (in fact, Fuzzilli somewhat tries to generate such code), but if someone did set up something like Fuzzilli for Jscript9, then it seems surprising if this was the bug they found with that.
On the other hand, a mutation-based fuzzer started with a corpus of old bug triggers might find something like this fairly quickly - see also (3) below - and it's also plausible that a generative fuzzer tuned to finding these kinds of issues would find this fairly quickly. It’s possible someone already had such a fuzzer from previous JIT work and could reuse it here.

2. Manual experimentation/analysis
If there is a way to inspect the JIT's intermediate code representation - or something roughly equivalent - in Jscript9, then it's very plausible that someone did a bit of grey-box analysis and manually experimented with how the JIT optimized different types of code. Then they would look at how it behaved for some of the typical edge-cases that a JS JIT has to deal with, in this case side-effect modelling, and would probably find this bug fairly quickly (trying to detach an ArrayBuffer during an indexed access through a type conversion seems like a pretty obvious thing to try). Since the bug is quite simple, this seems plausible. If the IR is not available, then it could still be possible to use this approach by using the assembly output and some tooling to simplify and find patterns. 

3. From public regression tests
The public test suites for e.g. v8 or JSC contain a multitude of bug triggers for old JS engine bugs. It’s possible that this exact bug was even covered by one of those tests. As such, it could also be that someone ported those tests to Jscript9, or even used them as basis for a mutation based fuzzer.

(Historical/present/future) context of bug:
There have been many recent 0-day exploits targeting JScript in Internet Explorer (CVE-2018-8653, CVE-2019-1367, CVE-2019-1429, CVE-2020-0674). This exploit differs from those in that it targets jscript9.dll, the default Javascript engine in IE9-11, rather than jscript.dll which was the default in IE8 and earlier. 

This vulnerability was chained with CVE-2020-0986 where CVE-2020-0986 was the elevation of privilege. CVE-2020-0986 was patched 2 months prior (June 2020) to CVE-2020-1380.

Areas/approach for variant analysis: 
  • Perform fuzzing, similar to Fuzzili, on Internet Explorer as long as it will still be in Windows builds. Fuzzili would have to be modified because it is built to Javascript engines that you have source code to.
  • To find very close variants, manually review other potential callback issues.
  • A less resource intensive option may be to port regression tests from other engines to Jscript9. 
Found variants: N/A

Structural improvements:
  • Internet Explorer is now considered “legacy” software. Remove it.
  • Attempt to break the JavascriptNative arrays/LargeHeapBlocks exploit method. Webkit attempts to do it with Gigacage. A similar approach implemented differently could likely have good results.

Potential detection methods for similar 0-days:
  • Look for Jscript scripts that run the same function hundreds of times in a loop in order to trigger JIT compilation.
  • Look for scripts that attempt to trigger garbage collection through “sleep” behavior.

Other references: 
Aug 2020: Kaspersky’s detailed blog post on the vulnerability being exploited and the exploit methodology. Includes POC.

No comments:

Post a Comment