I've been busy lately and I know I've promised a decent write-up on Wii U exploitation, but even though I've began writing it up, I must confess the motivation to finish it is deeply lacking (starting to understand derrek now).
So, as a way to compensate for that (and also to buy me extra time), I'm presenting something else that many have been waiting for (or not, I really don't know): a new Vita related post!
I still plan to finish up my work on the Wii U and document a lot of stuff I had been working on (previously unknown hardware registers, the RTC, and so on). I'll be pushing some of that to wiiubrew.org from time to time (nothing major, so don't get your hopes high).
Anyway, on to what brought you here, the Vita.
As you may recall, my last blog post about the Vita was a small recap of the HENkaku challenge and also my plans for the future. One of my main goals after winning Team molecule's challenge was to push further and defeat the next layer of security found on the Vita: the TrustZone.
I achieved this feat a while back and made a small remark about it on twitter, but I'm here now to fully explain how it was done and what it really means.
This is being published with the permission of Team molecule's members and doesn't conflict with their past and/or present work.
Once the HENkaku challenge was over, Team molecule released a lot of useful tools and a full blown framework to write and execute code on the Vita's kernel. Obviously, all these efforts were directed towards the firmware version 3.60.
I began by setting up the taiHEN framework and writing a small kernel application to fuzz SMCs (Secure Monitor Calls). Analogous to how SVCs (Supervisor Calls or syscalls) work, SMCs are present on ARM based systems that implement the Secure Monitor mode.
The Vita implements a complex security scheme with many layers of execution, but, for the purposes of this write-up we can divide them as:
- Userland: The least trusted environment on the Vita's ecosystem. This is where user applications such as the browser run in.
- System: System applications and SceShell run on this level. While this level is still part of the userland, there are special permissions granted to applications running here and communication with the next level on the chain is made easier due to the access to more important SVCs.
- Non-secure World Kernel (dubbed lv2): This is where all kernel level modules ("os0:kd") are loaded into and executed. It can be seen as the system's kernel in it's traditional sense, since this level is responsible for the sensitive operations often locked away from direct user interaction (thread and process management, filesystem, memory, and so on).
- Secure World Kernel (dubbed lv1): This level is commonly found on modern devices (such as smartphones) and provides a more trusted environment than the regular kernel. It's often used for highly sensitive operations such as cryptography and hardware communication.
- F00D (dubbed lv0): Not much is known about this level, but it's hypothesized to be a separate CPU with the sole purpose of handling crypto operations and executing secure modules.
So, with HENkaku we had an exploit chain that was capable of defeating the userland and run code on the non-secure world kernel (lv2). From there, we would get a privileged application running at the system level that we could communicate with for various purposes.
After the challenge was over, Team molecule released taiHEN, a full blown CFW framework that could be used as basis to write kernel level patches to be run on the Vita.
This left lv1 and lv0 left to exploit (not counting with the bootloaders) in order to fully take over the Vita's chain of trust.
For a while, I focused on passing along random data to as much SMCs as possible. Unfortunately, there aren't many registered SMCs which makes the attack surface from lv2 to lv1 very limited.
This was going nowhere on firmware 3.60 as I kept getting error codes from lv1 due to sane validation of my inputs.
At this point, I knew my best option would be to track down a Vita with a low firmware version since the very first successful efforts on taking down lv1 were done on such units (see https://wiki.henkaku.xyz/vita/Molecule).
The quest for the right firmware
I've had low firmware Vitas on my hands before (back when I was taking the HENkaku challenge), but I had to go further than that. My ideal goal was to find a launch day Vita, since it would be as unpatched as it can possible be.
Locating one turned out to be very hard and the ones available ended up being too expensive, so I decided to go after something a bit higher.
One thing I knew for sure: looking into the update files for the Vita (psp2-updatelist.xml), you can see that lv0 (the F00D processor) was updated once and only once on firmware version 1.60. Regardless of what this may mean, I wanted to find an unit on something lower than that just in case some critical lv0 bug was patched there.
Fortunately, I managed to track down an unit on firmware version 1.50 and so the journey began...
On such an old firmware version, the browser was based on a quite old WebKit version which means the most recent exploits (sort() method, CSSSelectorList) can't be used there.
Still, Team molecule's Davee had worked on such low firmware versions and by chaining together two bugs they were able to leak memory and do ROP under the browser application (see https://wiki.henkaku.xyz/vita/Vulnerabilities#WebKit_531_.28Vita_FW_BEFORE_2.00.29). This was also how roptool (a tool for assembling ROP chains) and HTMLit (a packager to convert ROP payloads into vulnerable HTML files) was born and, obviously, these tools served as basis for all my next exploitation efforts.
I also had my work cut short thanks to a roptool target for firmware 1.50 being already available (see https://bitbucket.org/ProximaPSP/wk150-roptool-target). This saved me from manually leaking the browser's memory and locating gadgets, so I skipped right into ROP.
NOTE: the most recent versions of roptool seem to have an issue when generating payloads for HTMLit, so I had to compile an older version of roptool to work on firmware 1.50.
Right in the kernel
Now I can do ROP inside the browser, but that's not even near enough. On firmware version 3.60, after compromising the browser, we could target the kernel by exploiting two specific bugs (which I've detailed during my HENkaku KOTH challenge posts). This time, we have a couple more options: https://wiki.henkaku.xyz/vita/Vulnerabilities#Kernel
First thing I did was testing out the sceIoDevctl bug on this low firmware and leak a few bytes from a kernel level thread's stack:
Note that on such low firmware versions userland ASLR was not as effective, kernel ASLR was not implemented at all and memory regions were allocated aligned to 0x1000 bytes. So, looking at that stack leak we can easily tell that SceSysmem is loaded at virtual address 0x00480000.
That's nice and all, but we need to be running code under the non-secure world kernel (lv2) if we want to even get to the same level as HENkaku.
Team molecule documented two old (and patched) bugs on the HENkaku wiki that look very attractive: a stack buffer overflow in sceSblDmac5EncDec and an integer overflow on the syscall handler itself.
Firmware 1.50 is still vulnerable to these bugs, so it seems only logical to try taking advantage of them. The sceNetSyscallIoctl UAF could still be an option, but exploiting it is far more convoluted in comparison.
So, time for an attack plan:
- Plant a ROP payload using sceIoDevctl;
- Use the sceSblDmac5EncDec stack buffer overflow and overwrite the current LR value;
- Jump to our ROP chain and profit!
Simple, but has many issues:
- Without a memory leak, we're essentially blind and ROP would be useless;
- We know where SceSysmem is, but that's not really helpful;
- sceSblDmac5EncDec is not imported by any userland module, so we can't even access it.
I was able to solve that last issue after finding a reference to sceSblDmac5EncDec here: https://wiki.henkaku.xyz/vita/PSVIMG
If this function is used to encrypt/decrypt PSVIMG files, then launching CMA should install the necessary SVC. However, it's still not imported anywhere so I have to brute-force syscall IDs (which are randomized) until I get it.
Still, even if I get this working and manage to jump into my ROP payload, what am I supposed to do? I need to find gadgets and that's not going to happen without a memory leak.
Eventually, I found a way to leak kernel memory, but at this point I had found the syscall table's location already so, I ditched my previous effort and focused instead on the syscall handler's integer overflow.
Passing a large value in R12 and executing the SVC instruction will overflow the syscall's table pointer, which means that we can force it to point into some module's import table and get an arbitrary function pointer dereferenced! In sum: arbitrary kernel code execution.
Taking down (Un)TrustZone
Now comes the fun part. With arbitrary kernel code execution, I'm essentially at the same level as HENkaku on firmware 3.60, which means it's time to fuzz SMCs once again.
In parallel, I dumped the entire non-secure kernel (lv2) memory space and began reverse-engineering several kernel modules. I had done this already for most of firmware 3.60 modules, but perhaps one of these older binaries could suggest something new.
I focused on kernel modules that invoked SMCs: SceKernelIntrMgr, SceKernelBusError, SceLowio and SceSblSmSchedProxy.
A call to the very first SMC (0x101) can be found inside SceKernelIntrMgr and it does something very interesting:
It seems to be used to set a physical memory region visible by both non-secure and secure worlds! Eventually, when reversing SceSblSmSchedProxy I figured out this region is used to send data back and forth across the two worlds.
This shared area is located on the non-secure kernel at the virtual address 0x00400000 and it's a 0x5000 sized buffer split into 160 blocks of 0x80 bytes each. Analyzing one block:
Hmm... Some values in there don't make much sense...
Anyway, back to the fuzzer. I decided to focus on the SMCs that SmSchedProxy invokes, mainly because they appear to access this shared region. Then, one particular SMC began displaying erratic behavior. It would cause a full kernel panic on some values, but would execute normally on others, so I decided to take a closer look:
The value in *(status_struct_addr + 0x04) seems to come from shared_mem_block + 0x14 (0x002A8109), but that's not a valid memory address. I tried to increment and pass similar values until I got a crash. After repeating this for a while, something became very clear:
- ((0x002A8109 - 0x01) << 0x01) == 0x00550210
I dumped part of the 0x00550000 region this way and found pointers to another region: 0x00510000. I tried passing it to the vulnerable SMC and, eventually, code was being copied! This SMC was leaking 0x08 bytes from an arbitrary lv1 memory region!
Unfortunately, despite my best efforts I couldn't find a reliable way to dump more than 0x08 bytes at a time (caching issues?), so it took me a loooong time to dump a few relevant modules.
I dumped SceSblSmSched first (which happened to be the module located at 0x00510000) so I went looking for the flawed function mapped as SMC 0x12F.
The function is located at virtual address 0x0051DDFC and to my surprise, it's simply doing an uncheck copy of 0x08 bytes from (smc_arg0 << 0x01) + 0x28 into a 0x80 byte aligned pointer to a shared memory block.
Upon taking a closer look, the shared_mem_index is also unchecked. So, not only the first argument can be an arbitrary address, but the second argument is an unchecked array index? This means we have an arbitrary(ish) memory read/write on lv1!
Looking into SceSblSmSched's import table I was able to locate SceSysmem (TrustZone version) and eventually the SMC table. After dumping enough of SceSysmem and locating some nice read/write primitives, I went back to the flawed SMC and tried to go the other way around: plant pointers to read/write primitives on the shared memory region, pass the shared memory region's address as the first argument for SMC 0x12F (since TrustZone is also able to access this region) and overflow the second argument (the index) to point somewhere into the SMC table.
This was not working and it took me a while to find out why: lv1 sees the shared memory region on a different address than lv2. I only figured that out after dumping a lot more of SceSysmem, which took me a few days...
Anyway, now I can do something like this:
- Write some pointers to 0x00400028 (shared_mem_blk0 + 0x28) and 0x0040002C (shared_mem_blk0 + 0x28 + 0x04). These pointers must be from TrustZone memory because Secure World can't access Non-secure World's memory space outside of this shared memory region.
- Call do_smc(shared_mem_addr, index, 0, 0, 0x12F). shared_mem_addr should be 0x002B0001 so it translates to 0x00560000 which is where the shared memory is located inside TrustZone (this is set inside the Sysroot region). index can be calculated by finding the SMC table, subtracting 0x00560000 (shared memory region) from it's address and multiplying the result by 0x80. This will overflow the destination across 0x80 sized memory blocks until it hits the SMC table and writes there.
- Now I should have two arbitrary pointers installed as SMC calls to do whatever I want.
Note that you can only read memory from lv1 into lv2, which means you still need to copy the memory from lv2 into userland (this can be easily achieved with sceKernelMemcpyKernelToUser).
I also thought about looking into the SMC handler inside SceKernelIntrMgr (TrustZone version) hoping they screwed up the same way they did with lv2 SVCs (not checking the R12 value), but unfortunately:
Still, that's pointless at this stage since this single bug granted me arbitrary lv1 code execution! After finding the necessary functions, I was able to allocate memblocks to plant lv1 payloads and run code freely on the Secure World!
And that's it. Another security layer peeled off from the Vita.
Unfortunately, this bug was patched somewhere around firmware version 1.80, which means it's absolutely useless for most users. Still, if you have a low firmware version and wish to dig deeper into it, this will help you taking down that nasty lv1.
I'm currently fuzzing the F00D's main interface on firmware 1.50 while trying to find new lv1 bugs on firmware 3.60. I'm also going to update the HENkaku wiki across the following days with all the information I've been gathering during my adventures (not just lv1 related).
I may release my 1.50 ROP scripts if necessary, but this post contains all the information necessary to recreate the exploit.
Please contact me for any clarification or doubts.
Until next time!