138 - Another iOS Bug and Edge Chakra Exploitation
strcat
was used in a callback to craft the xpath
for each element and it did this without any bounds checking. By nesting XML structures they could eventually overflow the memory region they were allocated in. As this region immediately preceded heap memory it was possible to overflow into the heap and overwrite the xml parser’s callback function pointers (approximately 11,000 bytes away) and kick off a ROP chain.
An uninitalized “Fast Tracker” in the Window’s HTTP Protocol stack as used by IIS. Despite providing a bit of a crash analysis and a POC the post is missing information about the vulnerability as their primary focus was on building out the exploit.
We start in HTTP!UlFastSendHttpResponse
which is used to send an HTTP Response. When the originating request includes the TE: trailers
header the function will allocate a “Fast Tracker” with a call to UlpAllocateFastTracker
. There is only an uninitialized use when the HTTP version is missing from the request line so presumably it was performing some conditional initialization there and when the version failed to match the usual suspects like HTTP/1.1
or HTTP/2
the value was just never initialized.
It is perhaps worth calling out that the TE:
header is important because it indicates the transfer encodings the client will accept. In HTTP/1.1
there are several options for this, but in HTTP/2
the only acceptable value is trailers
. So there is the chance that there is some sort of HTTP version confusion going on also.
With an uninitialized “Fast Tracker” and control over the contents through heap spraying with other requests they could gain control over the argument to a MmUnmapLockedPages
call. Resulting in a page fault with a bad pointer and a denial of service attack. Which is perhaps a useful primitive for a more complex chain abusing this for a sort of use-after-free situation although I’m not familiar enough with the Windows Kernel to comment on the nuances of MmUnmapLockedPages
for such a use.
There is a good deal of complexity in the object structure that is detailed in the post that I’m going to gloss over. Effectively you have an array of entry objects, and each entry has a pointer to a user_data_value_element
. Since there is generally a one-to-one mapping of entries to these user data values only the entry is reference counted.
There is a race condition in the allocation process for a new entry where a pointer to the user_data_value_element
has been claimed but not yet accounted for by the entry. During this window, another thread can drop the last reference to the entry resulting in the user_data_value_element
getting unexpectedly freed.
To prevent this the value element tracks the number of pointers handed out in an e_made
field, and the entry tracks the count. Then when the last reference is dropped before it free’s the user_data_value_element
it checks these two counts, and if there is a desync it knows that it is not safe to free the user_data_value_element
.
And so we have the core bug in that the e_made
field of user_data_value_element
is incremented without holding the appropriate lock. So you can have threads that should increment e_made
both read the same value and add one to it, resulting in an e_made
that is too low. Creating a situation where the value element can be inappropriately freed.
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 bug
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
object. 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 DataView
’s backing buffer
pointer to point to another DataView
, to provide the attacker a “view” of read/write capability into the metadata of the second DataView
.
The first DataView
now gives very useful primitives. You can use it to leak the second DataView
’s vftable
(virtual function table) pointer for infoleak, and you can smash the second DataView
’s backing buffer
pointer to create an arbitrary read/write primitive.
Code Exec
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 javascriptLibrary
object which contains a reference to the 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.