Here it is, the first vulnerability I exploited which affects SPIDER (O3DS).
In this post I will go through the process of identifying the vulnerability and getting a rop-chain execution.
I might post later about how to get real code execution, but since it’s all about abusing GPU’s DMA and it’s used by all userland exploits I’m sure you can figure it all by yourself.
The crash PoC
Looking at the several webkit testcases that crashed SPIDER, I found this pretty interesting one:
You might wonder why this specific test is so interesting… well… among all the nullptr and weird complicated bugs that showed up this one was simple enough to directly understand what was wrong.
Let’s take a look at this, the bug clearly occurs when executing
validity.valueMissing. It seems it’s trying to access something from the variable
validity which comes from a node deleted just before the bug occurs… clearly a Use-after-Free!
The bug actually occurs because
valueMissing is an interface for the
valueMissing function of the
control element - which get freed when deleting the node. After deleting the node, the
ValidityState object associated to the
validity variable is still allocated and holds a pointer to the freed
control object, thus leading to a Use-after-Free.
For more details about this bug you might want to read the patch changelog.
Spraying the Heap
Now that we have a basic understanding of what is going on, we need to re-allocate and write over the memory associated to the freed
control element. Our goal is to overwrite the
valueMissing field to get and arbitrary jump.
To allocate and write arbitrary data on the heap, one can use the
unescape function to allocate some simple strings.
Let’s try to spray the heap then with this simple example:
First, we force the removed element to be garbage collected so we can re-allocate its memory, then we allocate a bunch of
\u4141\u4141 strings and hope to overwrite what we want… well it won’t work.
The heap is divided into buckets and each bucket has its own block size, so when you allocate on the heap your newly allocated object will be stored in the most appropriate bucket - based on the size you are allocating.
So if we want to successfully overwrite the
control element’s memory we have to allocate a string whose length is close enough to that of the freed object.
I was clearly too lazy to find the exact size of the element so I tried a couple of different sizes and finally found that allocating 192 bytes made the browser crash with
Getting an arbitrary jump
So now it is time to gain control of the execution flow. We already know that calling
v.valueMissing trigger a virtual method call in the overwritten object, since we control that object we can easily replace the pointer to its vtable.
Let’s take a look at the related assembly code:
This is great because when branching
r0 still points to the overwritten object and we might be able to load values from there later. We can get an arbitrary just fairly easily since we can just overwrite the vtable pointer but we still have to find a way to stack pivot from there.
So, I searched for some gadgets and functions in the SPIDER binaries in the hope to find something useful to gain control of
sp. Fortunately I found the
magic_func, trust me it is really magic, let’s take a look at it:
With this function, one is able to load arbitrary values in
lr from the address pointed by
Thus we know where to jump but still need to setup the fake vtable, do we? Hum let’s search for the address of
magic_func in the binary before… well there is a pointer to this function in the .rodata section :D - let’s call this location
Here is the strategy:
- overwrite the vtable pointer and replace it with
- set the appropriate fields of the overwritten object so they will be loaded in
valueMissing to trigger the vulnerability.
Let’s simulate it on the associated assembly code:
This way we directly gain control of the execution flow without spraying additional buffers.
Here is the final exploit PoC:
u32_to_unicode function just converts an integer to a unicode string for the
spray takes in parameter the string to be allocated - the fake object - and
ropsetup writes the rop-chain but this part will be detailed in another post.
Exploiting this vulnerability was quite easy since the bug was quite simple and only required a single jump to successfully gain control of the execution flow. The next post will be dedicated to the SKATER bug which is a bit harder to understand and exploit :)