Wednesday, May 10, 2017

Analysis of the protection of Sony PlayStation 4

Don't know what to write - so will writhe about analysis of the protection of Sony PlayStation 4.

There will be a lot of codes and programs names. I will bring few links to software too. All right - lets start.

https://github.com/CTurt/PS4-playground

All stuff for hacking PS4.

Known facts about PS4.

As you most likely know, the PS4 uses a special eight-core x86-64 CPU from AMD, about the architecture of which is published a lot of research, and even if this particular version of the processor is slightly different from the standard standard, it will hardly be noticeable. For example, the PFLA (Page Fault Liberation Army) at 29C3 (29th Chaos Communication Congress) demonstrated a proof-of-concept proof that it is possible to implement a complete Turing machine using only page faults and x86 MMU, the video is available on YouTube . It will be interesting for those who, running the code in a virtual machine, at the same time wish to execute instructions on the CPU of the host.

And we are dealing not only with the well documented architecture of the CPU - the software used in PS4 is mostly related to open source.

For us, the most important thing is that Orbis OS, on which the console runs, is based on FreeBSD and uses separate parts of NetBSD, repeating the situation with PS3 in this regard; In addition to FreeBSD 9.0, Mono VM and WebKit are used from other notable large software.

Entry point - WebKit

WebKit is an open engine for rendering web pages in browsers for iOS, Wii U, 3DS, PS Vita and PS4.

Despite such a wide application and maturity of the project, WebKit does not lack individual vulnerabilities; Most of them you can find out by Pwn2Own records.

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

In 2014, nas and Proxima said that they successfully ported this exploit for use on the PS4 browser, and laid out the PoC code in public, than they initiated the process of hacking the PS4.

This code gives you random access to reading and writing everything that the WebKit process can read / write, and this in turn can be used to dump modules and overwrite return addresses on the stack, allowing us to set control over the command counter (for ROP).

Since that time, many other vulnerabilities have been discovered in WebKit, which supposedly allow dumping modules and ROP on the latest PS4 firmware, but at the time of writing none of these exploits were ported to PS4.

What is ROP (return oriented programming)?

Unlike primitive devices like DS and PSP, PS4 uses a kernel that controls the options of different memory areas. Memory pages marked as executable can not be overwritten; Pages marked with writable can not be executed; This principle is known as Data Execution Prevention (DEP).

For us, this means the impossibility of using a simple path: copying the payload into memory and then executing it. However, we can execute code that is already loaded into memory and marked as executable.

By itself, the ability to jump to one address is not particularly useful if we can not write our own code at this address - that's why we'll resort to ROP.

Return-oriented programming (ROP) is just an improved version of the traditional stack smashing, but instead of overwriting one value that a PC jumps on, we can link together many different addresses, known as "gadgets"

Usually, a gadget is just the one desired design, followed by ret.

In assembler x86_64, when execution reaches the ret instruction, the 64-bit value is popped off the stack and the PC jumps on it; Since we can control the stack, we can force each ret instruction to jump to the next desired gadget.

For example, starting from 0x80000, instructions can be stored:

mov rax, 0
ret

And since 0x90000 the following instructions are stored:

mov rbx, 0
ret

If we overwrite the return address on the stack so that it will store 0x80000 and after 0x90000, as soon as execution reaches the first ret instruction, it will jump to mov rax, 0, and right after that the next ret instruction will pop out of the 0x90000 stack and jump to mov rbx , 0.

Thus, this chain will play into our hands and set both registers rax and rbx to 0, as if we just wrote the code in one place and executed sequentially.

ROP chains are not limited to an address list; Suppose that 0xa0000 is followed by the following instructions:

pop rax
ret

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

Gadgets are also not required to end with ret instructions; We can use gadgets that end with jmp:

add rax, 8
jmp rcx

Having done so that rcx points to the ret statement, the chain will be executed in the usual way:

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

Sometimes you will not be able to find exactly the gadget that you need, in itself - only with other instructions after it. For example, if you want to set r8 to some value, but you only have this gadget, then you'll have to set r9 to some dummy value:

pop r8
pop r9
ret

Although from time to time you will have to show your creativity when writing ROP-chains, nevertheless, it is generally assumed that if you use a sufficiently large code dump, the received gadgets will be sufficient for the Turing-complete functionality; This makes ROP a viable way to bypass DEP.

Search for gadgets

To understand ROP, the following metaphor will help you.

Imagine that you are writing a new chapter in the book, while using only the words that stood at the ends of the sentences of the previous chapters. Obviously, by virtue of the construction of phrases, you can hardly find the words "and" or "or" at the end of one sentence - but we need these connecting elements if we want to write something meaningful.

It is entirely possible, however, that one of the proposals ended with the word "sand". And, though according to the author's idea, we should read this word entirely starting with the letter "s", if we start our reading with "a", then by pure chance we will get a completely different word - "and", which we required.

These principles also apply to ROP.

Since the structure of almost all functions looks like this:

;Saving registers
push    rbp
mov     rbp, rsp
push    r15
push    r14
push    r13
push    r12
push    rbx
sub     rsp, 18h
;Functions body
;Registry Restore
add     rsp, 18h
pop     rbx
pop     r12
pop     r13
pop     r14
pop     r15
pop     rbp
ret

Therefore, you should expect to find only pop gadgets, or, less often, xor rax, rax, which set the value to 0 before returning.

Comparison of

cmp [rax], r12
ret

Does not make any sense, because the result of the comparison is not used by the function. However, the likelihood of finding such gadgets is still there.

The x86_64 instructions are similar to words in that they have a variable length, and can mean completely different things depending on where the decoding starts.

The x86_64 architecture is a set of variable-length CISC instructions. Return-oriented programming on x86_64 takes advantage of the fact that the instruction set is very "dense" - in the sense that any arbitrary sequence of bytes can most often be interpreted as a 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 will look like if we start decoding with 0x52be14:

000000000052BE14                 cmp     [rax], r12
000000000052BE17                 ret

Although this code was never intended to be executed, it is located in the memory area that was marked "executable", which makes it very attractive for use as a gadget.

Of course, it would be incredibly expensive to waste time searching for all possible ways of interpreting the code before each manual ret; For us it is able to make existing utilities. To find ROP gadgets I prefer to use rp ++; To generate a text file filled with gadgets, simply enter the command:

rp-win-x64 -f mod14.bin --raw=x64 --rop=1 --unique > mod14.txt

Segmentation errors

If we try to execute an uncompleted page of memory, or we try to write it to an unrecordable page of memory, there will be a segmentation error.

For example, it looks like an attempt to execute code on the stack, which is "read" and "write" only (rw):

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

chain.add(chain.data);

And here it is - an attempt to write code that is "zamappen" only for reading and executing (rx):

setU8to(moduleBases[webkit], 0);

If a segmentation error occurs, the message "Insufficient free system memory" appears on the screen and the page does not load:

The cause of output of this message can be something else - for example, execution of an incorrect instruction or an unrealized system call, - but more often it comes out precisely because of a segmentation error.

ASLR

Address Space Layout Randomization (ASLR) is a security technology used in operating systems, which randomly changes the location of important structures in the address space of the process, namely: the image of the executable file, the libraries to load, the heap and the stack. Because of it, the base addresses of the modules change each time you start your PS4.

I received evidence that ASLR was disabled in the oldest firmware versions (1.05), but it appeared somewhere around 1.70. Note that ASLR for the kernel is disabled, at least for firmware versions 1.76 and below, and this will be proved further.

For most exploits, ASLR becomes a problem, because if you do not know the gadget addresses in memory, you will not know what to write to the stack.

Fortunately for us, we are not limited to writing static ROP-chains. We can use JavaScript to read the module table, which will help us get the base addresses of the loaded modules. Using these addresses, we can calculate the addresses of all our gadgets before running the ROP chain, bypassing ASLR.

The module table also includes the names of the module files:

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 mostly uses the [Signed] PPU Relocatable Executable ([S] PRX) format for modules, the lines referencing object files are noted in the libSceSysmodule.sprx dump. [Signed] Executable and Linking Format ([S] ELF ) - bdj.elf, web_core.elf and orbis-jsc-compiler.self.

This combination of modules and objects resembles the one used in the PSP and PS3.

A complete list of all available modules (and not just those that are loaded by the browser) can be found in libSceSysmodule.sprx. We can download and deface some of them thanks to several special system calls for the authorship of Sony, which will be discussed further.

JuSt-ROP

Using JavaScript to write and execute dynamic ROP-chains gives us a huge advantage over the usual buffer overflow attack.

In addition to bypassing ASLR, we can read the user agent of the browser, and substitute another ROP-chain for another version of the browser, giving our exploit the highest degree of possible compatibility.

We can use JavaScript even to read memory at our gadget addresses in order to make sure they are correct, which gives us almost perfect reliability.

Dynamic writing of ROP-chains makes sense in comparison with their preliminary generation by a script.

For these reasons, I created my own JavaScript framework for writing ROP-chains, JuSt-ROP.

Pitfalls JavaScript

JavaScript uses the representation of numbers in the format of double precision (64 bits) IEEE-754. This gives us 53 bits of accuracy (the mantissa VT_R8 has only 53 bits), which means that it is impossible to display each 64-bit value - some of them will have to apply an approximation.

If you just need to set the 64-bit number to some small value, like 256, then setU64to will cope with the task. But for cases where you need to write a buffer or data structure, there is a possibility that individual bytes will be written incorrectly if they were written in blocks of 64 bits. Instead, you need to write data in blocks of 32 bits (remembering that PS4 uses the order of little-endian) to make sure that each byte is identical.

System calls

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

Register                                          Value
Rax                                                 Number of the system call
Rdi                                                  Argument 1
Rsi                                                   Argument 2
Rdx                                                 Argument 3
R10                                                 Argument 4
R8                                                   Argument 5
R9                                                   Argument 6

We can try to execute any system call using the 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("pop rbp", stackBase + returnAddress - (chainLength + 8) + 0x1480);
                this.add("mov r10, rcx; syscall");
}

The use of system calls can tell us a lot about the PS4 core. Moreover, the use of system calls is the only way we can interact with the kernel, and potentially can execute a kernel exploit.

If you reverse engineer modules to identify some of Sony's special system calls, you can detect an alternative call format:

Register                         Value
Rax                                0
Rdi                                 System call number
Rsi                                  Argument 1
Rdx                                Argument 2
R10                                Argument 3
R8                                  Argument 4
R9                                  Argument 5

Apparently, Sony has done so for easy compatibility with the agreement on calling functions, for example:

unsigned long syscall(unsigned long n, ...) {
                register unsigned long rax asm("rax");
               
                asm("mov r10, rcx");
                rax = 0;
                asm("syscall");
                return rax;
}

Using this approach, they can perform any system call from C.
When writing ROP chains, we can use the following convention:

//Both commands return the ID of the current process:

chain.syscall("getpid", 20);
chain.syscall("getpid", 0, 20);

This is useful to remember in case you can choose the most convenient of available gadgets.

getpid

A single system call at number 20, getpid (void), is already able to tell us a lot about the kernel.

The very fact that this system call is working tells us that Sony did not even bother to mix system call numbers as required by the "security through ambiguity" technique (and under the BSD license they could do this without publishing new system numbers on the Internet Calls).

Thus, we automatically got our hands on the list of system calls that you can try to make in the PS4 kernel.

Secondly, by calling getpid (), restarting the browser, and then calling it again, we get a return value 1 greater than the previous one. Although FreeBSD supports PID randomization since version 4.0, sequential PID allocation is the default behavior. Apparently, Sony has not bothered to increase its protection here either, as it was done in projects like HardenedBSD.

How many system calls are there?

The last system call on FreeBSD 9 is wait6 behind the number 523; All that has the number above - special system calls Sony.

Attempting to call any of Sony's special system calls without valid arguments will return 0x16 error, "Invalid argument"; However, any compatible system calls, or unrealized system calls, will result in a "There is not enough free system memory" error.

Through trial and error, I found out that the system call numbered 617 is Sony's last call, all calls are no longer implemented.

Based on this, we can make a logical conclusion that in the PS4 kernel there are 85 special system calls (617 - 532) for the authorship of Sony.

This is significantly less than it was in the PS3, which had almost 1000 system calls in general. Well, even if this indicates a lesser scope for potential attack vectors, but it will be easier for us to document all the calls.

We go further. 9 of these 85 system calls always return 0x4e, ENOSYS, which means a simple thing - these calls only work on test devices for developers, leaving us with only 76 useful calls.

Of these 76, libkernel.sprx only refers to 45 (all applications that are not part of the kernel use this module to make system calls). Total, the developer has only 45 available special system calls.

Interestingly, although only 45 calls were intended for use (since libkernel.sprx has wrappers for them), some of the remaining 31 are still accessible from the browser process. It is possible that in these unintentionally abandoned calls, the probability of finding a vulnerability is much higher, since the testing of the time has obviously taken the least.

libkernel.sprx

In order to understand how special kernel calls are used by the kernel, the main thing is to remember that this is just a modification of the standard FreeBSD 9.0 libraries.

Here is the excerpt of the _libpthread_init code 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 on offset 0x215F0 from libkernel.sprx. Here's how the above code looks in the 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 for system call analysis

Libkernel is not fully open: it includes a large amount of Sony's own code that could reveal their system calls.

Although the analysis process will differ depending on the selected system call, for some of them it is fairly easy to find out the composition of the arguments that are passed to the call.

The wrapper of the system call will be declared somewhere in libkernel.sprx and will almost always follow the following pattern:

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 instruction mov r10, rcx does not necessarily mean that the system call takes at least 4 arguments; This instruction is for all wrappers of system calls, and even for those that do not accept any arguments - for example, getpid.

Once you have found the wrapper, you can look at the 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 a good idea to look for a few more pieces, just to make sure that the registers have not been changed 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

We see how with the enviable consistency of the first three registers from the system call convention (rdi, rsi, and rdx), so we can confidently state that the call takes three arguments.

For understanding, here's how we will reproduce these calls using JuSt-ROP:

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

Like most system calls, these calls will return 0 if successful, as seen in the code above, where jz performs the transition after the test return value.

Finding out something more complex than the number of arguments will require a much deeper analysis of the code before and after the call to understand the context, but the narrative should be enough for you to start.

System call brute force

Despite the fact that reverse-engineering of module dumps is the most reliable way to identify system calls, some of them are not mentioned in the dumps, so we have to analyze them blindly.

If we assume that a particular system call can take a certain set of arguments, then we can generate a brute force on all system calls that return a certain value (0 for success) with the selected arguments, and ignore all returned errors.

We can also pass zeros to all arguments, and brute all system calls that return useful errors like 0xe, "Bad address", which indicate that calls take at least one pointer.

First, we need to execute the ROP-chain as soon as the page loads. We can do this by hanging our function on the onload of the body element:

<body onload="exploit()">

Next, we need to perform a special system call, depending on the value from the HTTP GET. Although this can be done with JavaScript, for simplicity I use PHP:

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

As soon as the system call runs, we can check the return value, and if it does not give us anything interesting, make a redirect 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 at the end will launch the brute force from the first Sony system call.

Although this method requires a lot of experiments, you can confidently say that it will allow you to find several system calls that you will be able to partially identify.

System call 538

As an example, let's look at the 538 system call without relying on dumps of any modules.

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

0 — 0x16, «Invalid argument»
1 — 0xe, «Bad address»

The pointer to 0 is initially 0x64, but with each page refresh the value is incremented by 1.

Other potential arguments that you can try to substitute for are PID, thread ID, and file descriptor.

Despite the fact that most system calls return 0 on successful execution, some of the calls return a value increasing with each new call - apparently, these calls allocate any resource, like a file descriptor.

The next step will be to monitor the data before and after the system call to determine if something was written in them.

Since there is no change in the data, we can assume with good conscience that this is an input.

Then we try to feed the method a long string as the first argument. You should try this with every input that you will be able to detect, since there is a probability of detecting buffer overflow.

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

We get the return value 0x3f, ENAMETOOLONG. Alas, we see that the system call correctly constrains the name (32 bytes including the NULL delimiter), but now we know that the method expects a string, not a structure.

Well, now we have a few ideas about what this challenge can do. The most obvious option is some action related to the file system (for example, a special version of mkdir or open), but this version is unlikely to work for us - after all, the resource is allocated before we have written any data to the index.

Let's try to check if the first parameter is by. We split it with several symbols / and see if it will allow us to pass a long string to the method: 

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

Since this call also returns 0x3f, we can assume that the first argument is not the path; This is the name for something that will be placed in memory and will receive a sequential identifier.

After analyzing other system calls, I found that all of the following have the same behavior: 

533
538
557
574
580

With the help of the received information, it is almost impossible to guess what exactly these system calls are doing, but if you do other tests, you will gradually disclose the secret. I'll save you a bit of time - the system call 538 allocates memory for the event flag (and takes as a parameter not only the name).

With the help of basic knowledge of how the kernel works, you can assume, and then check what the system allocates memory with system calls - semaphores, mutextors, and so on.

Dump additional modules

We can dump additional modules as follows:
Download the module
We get the base address of the module
Dump the module.

All results are on psdevwiki - just google this word.

To load the module, we need to use the sceSysmoduleLoadModule function from libSceSysmodule.sprx + 0x1850. The first parameter is the identifier of the loadable module, in the remaining three simply passes 0.

The following JuSt-ROP method is useful for this 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("pop rbp", stack_base + return_va - (chainLength + 8) + 0x1480);
                this.add(module_bases[module] + address);
}

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

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

Like most system calls, this one should return 0 on success. To see the identifier of the module allocated in memory, we can use one of the Sony system calls at number 592 to get a list of the 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 code without loading any additional modules will display the following list:

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

However, if we run it after loading the 0xb module, we'll see an additional element, 0x65. Remember - the module ID is not the same as the ID of the loaded module.

Now we can use another Sony system call at number 593, which takes the loaded module ID and the buffer, and fills the buffer with information about the loaded module, including its base address. Since the ID of the loaded module is always 0x65, we can "cram" it into our chain, instead of storing the result from the list of modules.

The buffer must begin with the size of the structure that should be returned, otherwise error 0x16 will return, "Invalid argument":

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

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

If successful, 0 will return, and the buffer will be filled with a structure that can be read like this:

var name = readString(moduleInfoAddress + 0x8);
var codeBase = getU64from(moduleInfoAddress + 0x108);
var codeSize = getU32from(moduleInfoAddress + 0x110);
var dataBase = getU64from(moduleInfoAddress + 0x118);
var dataSize = getU32from(moduleInfoAddress + 0x120);

Now we have everything necessary for the module dump!

dump(codeBase, codeSize + dataSize);

There is another Sony system call, under the number 608, which works similar to the 593 way, but provides a little bit more information about the loaded module:

setU64to(moduleInfoAddress, 0x1a8);
chain.syscall("getDifferentModuleInfo", 608, 0x65, 0, moduleInfoAddress);
logAdd(hexDump(moduleInfoAddress, 0x1a8));

It is not known what this information can mean.

Let's explore the file system

PS4 uses standard FreeBSD 9.0 system calls to read files and directories.

However, despite the fact that reading separate directories like / dev / will work, reading others - for example, / - is not.

I do not know why this happens, but if you use gendents instead of read for directories, then everything will work 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, 1028);

Here is the resulting 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  ................

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

You can also parse this memory and get a list of entities; Take a look at the browser.html from the repository, which acts as a file manager:

Alas, because of the sandbox, we do not have full access to the file system. An attempt to read files or directories that exist, but access to them is limited, will return error 2, ENOENT, "No such file or directory". However, we can still get access to various interesting things - encrypted save files, trophies and account information.

Sandbox

The problem with the operation of system calls is not limited to individual paths - there are other reasons why they can not be executed.

Most often, a prohibited system call simply returns error 1, EPERM, "Operation not permitted"; This statement is valid for calls like ptrace, because other system calls will not work for a variety of reasons.

Compatible system calls are disabled. For example, if you want to call mmap, you should use system call number 477, not 71 or 197; Otherwise, you will get a segment.

Other system calls, like exit, will also cause a segmentation fault:

chain.syscall("exit", 1, 0);

An attempt to create an SCTP socket will return a 0x2b, EPROTONOSUPPORT error indicating that the SCTP sockets were turned off in the PS4 kernel:

//int socket(int domain, int type, int protocol);
//socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
chain.syscall("socket", 97, 2, 1, 132);

And, although calling mmap with PROT_READ | PROT_WRITE | PROT_EXEC returns a valid pointer, the PROT_EXEC flag will be ignored. Reading its protection will return 3 (RW), and any attempt to execute memory will result in the segment:

chain.syscall("mmap", 477, 0, 4096, 1 | 2 | 4, 4096, -1, 0);
chain.write_rax_ToVariable(0);
chain.read_rdi_FromVariable(0);
chain.add("pop rax", 0xfeeb);
chain.add("mov [rdi], rax");
chain.add("mov rax, rdi");
chain.add("jmp rax");

The list of open source software used in PS4 does not include specialized software for sandboxes like Capsicum, so PS4 either uses "clean" jails from FreeBSD, or relies on its own proprietary system for isolating environments (which is unlikely).

http://doc.dl.playstation.net/doc/ps4-oss/

Jail

We can prove the existence of active use of jails from FreeBSD in the PS4 kernel with the auditon system call, which can not be performed in an isolated jailed environment:

chain.syscall("auditon", 446, 0, 0, 0);

The first thing that the audition system call does is check jailed here, and if so, it returns ENOSYS:

if (jailed(td->td_ucred))
                return (ENOSYS);

Otherwise, the system call will most likely return EPERM from mac_system_check_auditon here:

error = mac_system_check_auditon(td->td_ucred, uap->cmd);
if (error)
                return (error);

Or from priv_check here:

error = priv_check(td, PRIV_AUDIT_CONTROL);
if (error)
                return (error);

The farthest point the system call can reach is right after priv_check, here, before returning EINVAL because of the argument length equal to 0:

if ((uap->length <= 0) || (uap->length > sizeof(union auditon_udata)))
                return (EINVAL);

Because mac_system_check_auditon and priv_check never return ENOSYS, the hit on the jailed check is the only option when ENOSYS returns.

When the chain is executed, ENOSYS (0x48) is returned.

This tells us that the sandbox system used by PS4 is at least based on jail because it uses jailed checks.

Exploits of the FreeBSD 9.0 kernel

There is not much point in finding new vulnerabilities in the source code of the FreeBSD 9.0 kernel, since several kernel exploits have been found since the release in 2012, to which the PS4 can potentially be vulnerable.

Some of them we can discard at once:

FreeBSD 9.0-9.1 mmap / ptrace - Privilege Escalation Exploit - does not work, because we do not have access to the ptrace system call.

FreeBSD 9.0 - Intel SYSRET Kernel Privilege Escalation Exploit - will not work, because the PS4 uses an AMD processor.
FreeBSD Kernel - Multiple Vulnerabilities - perhaps the first vulnerability in this package will work, but the other two rely on SCTP sockets, which are disabled in the PS4 kernel, as mentioned earlier.

Fortunately, there are a few smaller vulnerabilities that can lead us to something interesting.

getlogin

One vulnerability that can be easily tried is the use of the getlogin system call to leak a small amount of kernel memory.

The getlogin system call is designed to copy the user name of the current session to user memory, however, because of the bug, the buffer is always copied completely, not just the size of the string with the name. This means that we can read some uninitialized data from the kernel, which may come in handy.

Note that the system call (49) is actually int getlogin_r (char * name, int len); and not char * getlogin (void) ;.

So, let's try to copy a bit of kernel memory to an unused part of the user's memory:

chain.syscall("getlogin", 49, chain.data, 17);

Alas, we can not get more than 17 bytes, because:

The length of the user name is limited to MAXLOGNAME (from <sys / param.h>) characters, currently 17 characters, including blank characters.

FreeBSD Man Pages

After the execution of the chain, the return value is 0, which means that the system call worked! A great start. Now let's take a look at the memory we pointed out:

Before completing the chain:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00

After completing the chain:

72 6f 6f 74 00 fe ff ff 08 62 61 82 ff ff ff ff
00

After decoding the first four bytes in ASCII:

root

It turns out that the browser is running from root! This is a surprise.

But what is even more interesting is that the leaked memory resembles a pointer to something in the kernel, which at each start of the chain remains the same; This testimony confirms the Yifanlu theory that PS4 does not have ASLR protection (address space randomization) at the kernel level!

Total

Judging by the information gathered, the PS4 core very much resembles the stock core of FreeBSD 9.0. It is important to note that the changes are more likely to change the standard kernel configuration than to modify the code. Although Sony also added several of its own special system calls to the core, the rest of the kernel appears to have remained virtually untouched.

For these reasons, I'm inclined to think that the PS4 has all the same "juicy" vulnerabilities as in the FreeBSD 9.0 kernel!

Unfortunately, most of the kernel exploits can not be performed from the WebKit entry point due to sandbox restrictions (which are most likely controlled by the standard jails engine from FreeBSD). Alas, we do not have to hope for the publication of private exploits for FreeBSD 9, so until suddenly a new one suddenly comes out, we are forced to work with what is. I assume that there is a possibility of exploiting the PS4 kernel using some of the existing vulnerabilities of memory corruption errors, but this will definitely not be easy.

The best approach here is reverse engineering of all the modules that will be obtained by dumping, in order to document the maximum possible number of special system calls from Sony; Intuition tells me that with them the chance to achieve success will be higher than with the standard system calls of FreeBSD.

Jaicrab recently discovered two UART ports on the PS4, which tells us about the potential interest of hardware hackers in the console. Although the role of hardware hackers was usually in the dumping of the RAM system (as it was with DSi), this time we already dealt with this task ourselves thanks to the exploit WebKit - however, it is possible to detect a kernel vulnerability that will be "turned on" by hardware, like this Was with the original hack of the hypervisor in PS3 for the authorship of geohot. However, this does not negate the fact that the PS4 kernel's exploit is likely to be made due to the vulnerability of the system call.


No comments:

Post a Comment