Wanna break Microsoft's Edge browser? Google's explained how

JavaScript just-in-time compilation and some memory meddling make a mess

Back in February 2018, Google's Project Zero went public with a Microsoft Edge bug that Redmond couldn't fix in time for its next patch release. Now, the Google researcher - Ivan Fratric - has provided a detailed technical explanation of the problem and says Microsoft's fix might not be adequate.

Fratric discovered that an interaction between just-in-time JavaScript compilation, Edge’s Chakra JavaScript engine, and Arbitrary Code Guard that gave attackers an arbitrary code execution vector.

Arbitrary Code Guard (ACG) is designed to prevent code being dynamically modified, and had its most recent enhancements in March 2017.


Google reveals Edge bug that Microsoft has had trouble fixing


Fratric explained the problem in this post last Thursday: “When ACG is applied to a Microsoft Edge Content Process, it makes it impossible to allocate new executable memory within a process or modify existing executable memory. The goal of this is to make it more difficult for an attacker who already gained some capabilities in the browser’s Content Process to execute arbitrary code.”

This white paper (PDF) explains Fratric’s attack in more detail: his attack vector related to how JavaScript just-in-time (JIT) compilation works when ACG is present.

Because JIT is incompatible with ACG, instead of running JIT as part of the Edge Content Process, Microsoft pulled the JIT Engine into its own process.

If the Chakra JavaScript engine encounters a function that needs JIT compilation, it passes the bytecode to the JIT Server, which “compiles the bytecode and writes the resulting executable code back into the calling process using shared memory.”

That, the white paper explained, lets the Content Process execute the JIT code “without violating the dynamic code policy.”

The other key to the attack surface is the Control Flow Guard (CFG), designed to protect against memory corruption vulnerabilities. The Fratric paper notes that one vector here is that “returns aren’t protected, so overwriting a return address is all that’s needed for a successful bypass. Normally, in order to be able to overwrite a return address on the stack, an attacker first needs to know where stack is. Chakra bytecode removes this requirement by including opcodes that can be used to read & write to the stack”.

There’s a full walk-through of various memory mappings and process interactions before we get to the attack scenario set out in the white paper:

  • Attacker observes the addresses for the JIT allocations and predicts the address of the next one;
  • Attacker unmaps the corresponding JIT section UnmapViewOfFile();
  • Attacker calls VirtualAlloc() to reclaim the memory, but this time with PAGE_READWRITE permissions;
  • Attacker writes their payload to a newly allocated location;
  • Attacker waits until the JIT server makes the memory region executable. After this, an attacker can simply transfer control flow to the code written in step 4.

As is so often the case, the fix wasn't as hard as Microsoft feared: Redmond just had to remove the VirtualAllocEx() call.

There’s a proof-of-concept here, and its only assumption was that “an attacker already managed to get a memory read/write primitive in the Content Process through an unrelated vulnerability.” ®

Biting the hand that feeds IT © 1998–2018