Exploiting the 3DS browsers - Part 2: validityhax
Intro
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.
Heap spraying is a common method used for browsers exploitation, we are going to allocate a bunch of javascript objects and hope the freed memory will be re-allocated for our new objects.
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 r0=0x41414141
:)
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.
Stack pivoting
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 sp
and lr
from the address pointed by r0
:).
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 MAGIC_FUNC_PTR
.
Here is the strategy:
- overwrite the vtable pointer and replace it with
MAGIC_FUNC_PTR-0x284
; - set the appropriate fields of the overwritten object so they will be loaded in
sp
andlr
; - call
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.
Exploit PoC
Here is the final exploit PoC:
The u32_to_unicode
function just converts an integer to a unicode string for the unescape
function, 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.
Conclusion
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 :)