Exploits & Vulnerabilities
CVE-2020-1380: Analysis of Recently Fixed IE Zero-Day
Save to Folio
Overview of Jscirpt9.dll Execution Pipeline
- The ByteCodeGenerator traverses the AST and generates ByteCode.
- The Interpreter is a virtual machine that executes ByteCode. Profile data, such as type information, is collected when ByteCode is executed.
- When some code snippets are invoked multiple times, like in a for-loop, the Interpreter sends the ByteCode and profile data to the backend Just-In-Time (JIT) engine to generate machine code and then replaces the ByteCode entry point with the generated machine code.
- When the machine code is executed, if some status breaks the profile assumption, the machine code will bail out to the Interpreter to execute the ByteCode again to avoid any safety issues.
Locating the Machine Code
When the loop count is greater than some threshold values (0x32 in the code snippet in Figure 3), the loop body and the inner call function, opt, will be sent to the backend JIT engine job queue to generate optimized machine code:
The backend JIT engine thread gets the job from the queue and finally calls jscript9!Func::Codegen to generate optimized machine code:
The backend JIT engine goes through several steps to generate optimized machine code, such as: build intermediate representation (IR), inlining, build control flow graph (CFG), data flow analysis, lower, allocate register, layout, encode, etc.:
When optimized machine code is generated, it will be used to replace the ByteCode loop body. When the for-loop is called next, the machine code will be invoked instead in function Js::InterpreterStackFrame::CallLoopBody:
Finally, the loop body machine code will invoke the inner call function opt’s machine code, which we can see here:
Root Cause Analysis of CVE-2020-1380
The PoC of CVE-2020-1380 is shown in Figure 8:
The following steps can trigger the bug:
- The for-loop sends the opt function to the JIT engine.
- In function opt, the three lines of “arguments operation” can set value2 to value1, then the Float32Array first element arr is set by value1.
- After function opt is sent to the JIT engine, it changes the arguments value2 from an integer 0x1337 to an object that has a valueOf callback function.
- In the final call of function opt, because the argument 'flag' is set to 0, the basic block of “if (flag == 1)” is not executed, which sets value2 to arr. Because an object replaces value2, implicit type transform happens, then the valueOf callback function may be invoked in machine code.
First, we change the three lines of “arguments operation” to “arguments = value2”, which has the same effect. Figure 9 shows the generated JIT code snippet.
Finally, the implicit call valueOf will be called directly from machine code. An attacker can use this no-check callback opportunity to trigger a UAF vulnerability, like neutering the TypedArray's ArrayBuffer memory by Worker thread.
Why the three lines of “arguments operation” can eliminate the DisableImplicitFlags setting machine code piqued my curiosity. I think the type inference error of arguments in the backend JIT GlobOpt phase is the root cause. The JIT engine doesn’t know the side effect of Array.prototype.push, which can be used to change the type of arguments. The type of arguments should be killed after Array.prototype.push operation to avoid this type inference error issue.
Trend Micro™ Deep Security™ and Vulnerability Protection protect users from exploits that target this vulnerability via the following rule:
- 1010441 – Microsoft Internet Explorer Scripting Engine Memory Corruption Vulnerability (CVE-2020-1380)
Trend Micro™ TippingPoint® protects customers through the following rules:
- 37955: HTTP: Microsoft Internet Explorer Use-After-Free