Three part blog series by Connor Mcgarr which covers exploiting a type confusion in Chakra-based Edge. Part 1 covers environment setup and the vulnerability, part 2 the core exploitation primitives, and part 3 porting the exploit to Edge and bypassing Edge mitigations.
The type confusion is a JIT bug where the engine incorrectly assumes certain opcodes like
InitProto for setting a prototype will have no side-effects, and thus allows a function that sets a prototype to be JIT’d. When an object is set as a prototype, it undergoes a type transition, and the inline properties end up getting converted into a pointer to auxiliary data slots. If this function gets recognized as a “hot path” (ie. calling it a bunch of times), it will be compiled and the normal JS type-checks will be removed.
This gives an attacker the ability to corrupt the auxiliary data slot pointer via the inline properties, since the JIT’d code has no idea the type changed.
Exploitation You won’t immediately be able to create fake pointers with this confusion. For one thing, you have no infoleak to defeat ASLR. For another, integers in JS are “NaN-boxed”, meaning the type of the value is encoded into the value itself. You can however create another type confusion by setting a property to an instance of an object. The engine will implicitly write a pointer to that object’s metadata there. This then extends your corruption logic to allow control over that object’s metadata.
In this case, they setup an object that points to a
DataView objects are useful because they’re essentially wrappers around raw buffers of memory with no NaN-boxing or JS type interference. From this point, it’s possible to corrupt the
buffer pointer to point to another
DataView, to provide the attacker a “view” of read/write capability into the metadata of the second
DataView now gives very useful primitives. You can use it to leak the second
vftable (virtual function table) pointer for infoleak, and you can smash the second
buffer pointer to create an arbitrary read/write primitive.
Control Flow Guard (CFG) is in-play here, but only the forward edge is protected. While you can’t attack the virtual function tables for code exec, you can leak a stack address through the
type field (which points to a
ScriptContext), and corrupt a return address on the stack for code exec. They set a return address to
WinExec() to pop a shell as a child process.
Part three talks about porting this exploit from the Chakra JS engine to Edge, which had more mitigations. For one thing, in Edge you can’t spawn child processes, and there’s also Arbitrary Code Guard (ACG), which prevents you from re-mapping a code page as writable or mapping an executable page. To bypass ACG, they chained with an existing n-day to write shellcode into the JIT server process which they could then jump to.