Nytro Posted August 21, 2015 Report Posted August 21, 2015 (edited) Analysis of PS4's security and the state of hackingSince there haven't been any major public announcements regarding PS4 hacking for a long time now, I wanted to explain a bit about how far PS4 hacking has come, and what is preventing further progression.I will explain some security concepts that generally apply to all modern systems, and the discoveries that I have made from running ROP tests on my PS4.If you are not particularly familiar with exploitation, you should read my article about exploiting DS games through stack smash vulnerabilities in save files first.You may download my complete setup here to run these tests yourself; it is currently for firmware 1.76 only.Background information about the PS4As you probably know the PS4 features a custom AMD x86-64 CPU (8 cores), and there are loads of research available for this CPU architecture, even if this specific version might deviate slightly from known standards. For example, PFLA (Page Fault Liberation Army) released a proof of concept implementing a complete Turing machine using only page faults and the x86 MMU during the 29C3 congress, check their awesome video over at YouTube. Also interesting if you are trying to run code within a virtual machine and want to execute instructions on the host CPU.- EurAsia news article 3251As well as having a well documented CPU architecture, much of the software used in the PS4 is open source.Most notably, the PS4's Orbis OS is based on FreeBSD, just like the PS3's OS was (with parts of NetBSD as well); but as well as FreeBSD 9.0, other noticable software used includes Mono VM, and WebKit.WebKit entry pointWebKit 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 and PSP, 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, 0retAnd from 0x90000 may contain instructions:mov rbx, 0retIf 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 raxretWe 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, 8jmp rcxBy 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 r8pop r9retAlthough 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 gadgetsThink 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 registerspush rbpmov rbp, rsppush r15push r14push r13push r12push rbxsub rsp, 18h; Function body; Restore registersadd rsp, 18hpop rbxpop r12pop r13pop r14pop r15pop rbpretYou'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], r12retWouldn'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.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.- WikipediaTo demonstrate this, take a look at the end of this function from the WebKit module:000000000052BE0D mov eax, [rdx+8]000000000052BE10 mov [rsi+10h], eax000000000052BE13 or byte ptr [rsi+39h], 20h000000000052BE17 retNow take a look at what the code looks like if we start decoding from 0x52be14:000000000052BE14 cmp [rax], r12000000000052BE17 retEven 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:rp-win-x64 -f mod14.bin --raw=x64 --rop=1 --unique > mod14.txtSegmentation faultsIf we do try to execute a non-executable page of memory, or try to write to a non-writable page of memory, a segmentation fault will occur.For example, trying to execute code on the stack, which is mapped as read and write only: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: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.ASLRAddress 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 , 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.selflibkernel.sprxlibSceLibcInternal.sprxlibSceSysmodule.sprxlibSceNet.sprxlibSceNetCtl.sprxlibSceIpmi.sprxlibSceMbus.sprxlibSceRegMgr.sprxlibSceRtc.sprxlibScePad.sprxlibSceVideoOut.sprxlibScePigletv2VSH.sprxlibSceOrbisCompat.sprxlibSceWebKit2.sprxlibSceSysCore.sprxlibSceSsl.sprxlibSceVideoCoreServerInterface.sprxlibSceSystemService.sprxlibSceCompositeExt.sprxAlthough the PS4 predominantly uses the [signed] PPU Relocatable Executable (PRX) format for modules, some string references to [signed] Executable and Linking Format (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-ROPUsing 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.I created a JavaScript framework for writing ROP chains, JuSt-ROP, for this very reason.JavaScript caveatsJavaScript 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 callsInterestingly, 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 numberrdi - Argument 1rsi - Argument 2rdx - Argument 3r10 - Argument 4r8 - Argument 5r9 - Argument 6We 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("pop rbp", stackBase + returnAddress - (chainLength + 8) + 0x1480); this.add("mov r10, rcx; syscall");}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 - 0rdi - System call numberrsi - Argument 1rdx - Argument 2r10 - Argument 3r8 - Argument 4r9 - Argument 5It is likely that Sony did this to have easy compatibility with the function calling convention, for example:unsigned long syscall(unsigned long n, ...) { register unsigned long rax asm("rax"); asm("mov r10, rcx"); rax = 0; asm("syscall"); return rax;}[FONT=Arial]Using this, they can perform any system call from C.[/FONT][FONT=Arial]When writing ROP chains, we can use either convention:[/FONT]// Both will get the current process ID: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.getpidJust 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 1 higher than the previous value. 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.sprxTo 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 getpidmov cs:dword_5B638, eaxcmp eax, 1jnz short loc_2169Fcall setsidcmp eax, 0FFFFFFFFhjz loc_21A0Clea rdi, aDevConsole ; "/dev/console"call revoketest eax, eaxjnz loc_21A24lea rdi, aDevConsole ; "/dev/console"mov esi, 2xor al, alcall openmov r14d, eaxtest r14d, r14djs loc_21A3Clea rdi, aRoot ; "root"call setlogincmp eax, 0FFFFFFFFhjz loc_21A54mov edi, r14dmov esi, 20007461hxor edx, edxxor al, alcall ioctlcmp eax, 0FFFFFFFFhjz loc_21A6CReversing module dumps to analyse system callslibkernel 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 near000000000000DB70 mov rax, 259h000000000000DB77 mov r10, rcx000000000000DB7A syscall000000000000DB7C jb short error000000000000DB7E retn000000000000DB7F000000000000DB7F error:000000000000DB7F lea rcx, sub_DF60000000000000DB86 jmp rcx000000000000DB86 syscall_601 endpNote 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, 10h0000000000011D55 xor esi, esi0000000000011D57 mov edx, 10000000000011D5C call syscall_6010000000000011D61 test eax, eax0000000000011D63 jz short loc_11D6AIt's good to look up several of these, just to make sure that the registers weren't modified for something unrelated:0000000000011A28 mov edi, 90000000000011A2D xor esi, esi0000000000011A2F xor edx, edx0000000000011A31 call syscall_6010000000000011A36 test eax, eax0000000000011A38 jz short loc_11A3FConsistently, 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 callsAlthough 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'sonload:<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 538As 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 1Other 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:533538557574580From 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 modulesWe can dump additional modules by following these stages:Load the moduleGet the module's base addressDump the moduleI 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("pop rbp", stack_base + return_va - (chainLength + 8) + 0x1480); 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, 0x59But 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 error 0x16 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 + 0x8);var codeBase = getU64from(moduleInfoAddress + 0x108);var codeSize = getU32from(moduleInfoAddress + 0x110);var dataBase = getU64from(moduleInfoAddress + 0x118);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, 0x1a8);chain.syscall("getDifferentModuleInfo", 608, 0x65, 0, moduleInfoAddress);logAdd(hexDump(moduleInfoAddress, 0x1a8));It's not clear what this information is.Browsing the filesystemThe 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, 1028);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_std00000e0: 6572 7200 1800 0000 1400 0209 6465 6369 err.........deci00000f0: 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_tty0000130: 3500 0000 1c00 0000 1400 0209 6465 6369 5...........deci0000140: 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_tty0000180: 6230 0000 2000 0000 1400 020a 6465 6369 b0.. .......deci0000190: 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.......noti0000250: 6669 6361 7469 6f6e 3000 0000 4600 0000 fication0...F...0000260: 1800 020d 6e6f 7469 6669 6361 7469 6f6e ....notification0000270: 3100 0000 5000 0000 1000 0206 7573 6263 1...P.......usbc0000280: 746c 0000 5600 0000 1000 0206 6361 6d65 tl..V.......came0000290: 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: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. I will go over more of the filesystem in my next article.SandboxingAs well as file related system calls failing for certain paths, there are other reasons for a system call to fail.Most commonly, a disallowed system call will just return error 1, EPERM, "Operation not permitted"; such as trying to use ptrace, but other system calls may fail for different reasons:Compatibilty system calls are disabled. If you are trying to call mmap for example, you must use system call number 477, not 71 or 197; otherwise a segfault will be triggered.Other system calls such as exit will also trigger a segmentation fault:chain.syscall("exit", 1, 0);Trying to create an SCTP socket will return error 0x2b, EPROTONOSUPPORT, indicating that SCTP sockets have been disabled 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 will return a valid pointer, the PROT_EXEC flag is ignored. Reading its protection will return 3 (RW), and any attempt to execute the memory will trigger a segfault: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 the PS4 doesn't list any kind of sandboxing software like Capsicum, so the PS4 must use either pureFreeBSD jails, or some kind of custom, proprietary, sandboxing system (unlikely).JailsWe can prove the existence of FreeBSD jails being actively used in the PS4's kernel through the auditon system call being impossible to execute within a jailed environment:chain.syscall("auditon", 446, 0, 0, 0);The first thing the auditon system call does is check jailed here, and if so, return ENOSYS:if (jailed(td->td_ucred)) return (ENOSYS);Otherwise the system call would most likely return EPERM from the mac_system_check_auditon here:error = mac_system_check_auditon(td->td_ucred, uap->cmd);if (error) return (error);Or from the priv_check here:error = priv_check(td, PRIV_AUDIT_CONTROL);if (error) return (error);The absolute furthest that the system call could reach would be immediately after the priv_check, here, before returning EINVAL due to the length argument being 0:if ((uap->length <= 0) || (uap->length > sizeof(union auditon_udata))) return (EINVAL);Since mac_system_check_auditon and priv_check will never return ENOSYS, having the jailed check pass is the only way ENOSYS could be returned.When executing the chain, ENOSYS is returned (0x48).This tells us that whatever sandbox system the PS4 uses is at least based on jails because the jailed check passes.FreeBSD 9.0 kernel exploitsIt makes little sense trying to look for new vulnerabilities in the FreeBSD 9.0 kernel source code because since its release in 2012, several kernel exploits have already been found, which the PS4 could potentially be vulnerable to.We can immediately dismiss some of these for obvious reasons:FreeBSD 9.0-9.1 mmap/ptrace - Privilege Escalation Exploit - this won't work since, as previously stated, we don't have access to theptrace system call.FreeBSD 9.0 - Intel SYSRET Kernel Privilege Escalation Exploit - won't work because the PS4 uses an AMD processor.FreeBSD Kernel - Multiple Vulnerabilities - maybe the first vulnerability will lead to something, but the other 2 rely on SCTP sockets, which the PS4 kernel has disabled (as previously stated).However, there are some smaller vulnerabilites, which could lead to something:getloginOne vulnerability which looks easy to try is using the getlogin system call to leak a small amount of kernel memory.The getlogin system call is intended to copy the login name of the current session to userland memory, however, due to a bug, the whole buffer is always copied, and not just the size of the name string. This means that we can read some uninitialised data from the kernel, which might be of some use.Note that the system call (49) is actually int getlogin_r(char *name, int len); and not char *getlogin(void);.So, let's try copying some kernel memory into an unused part of userland memory:chain.syscall("getlogin", 49, chain.data, 17);Unfortunately 17 bytes is the most data we can get, since:Login names are limited to MAXLOGNAME (from <sys/param.h>) characters, currently 17 including null.- FreeBSD Man PagesAfter executing the chain, the return value was 0, which means that the system call worked! An excellent start. Now let's take a look at the memory which we pointed to:Before executing the chain:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000After executing the chain:72 6f 6f 74 00 fe ff ff 08 62 61 82 ff ff ff ff00After decoding the first 4 bytes as ASCII:rootSo the browser is executed as root! That was unexpected.But more interestingly, the memory leaked looks like a pointer to something in the kernel, which is always the same each time the chain is run; this is evidence to support Yifanlu's claims that the PS4 has no Kernel ASLR!SummaryFrom the information currently available, the PS4's kernel seems to be very similar to the stock FreeBSD 9.0 kernel.Importantly, the differences that are present appear to be from standard kernel configuration changes (such as disabling SCTP sockets), rather than from modified code. Sony have also added several of their own custom system calls to the kernel, but apart from this, the rest of the kernel seems fairly untouched.In this respect, I'm inclined to believe that the PS4 shares most of the same juicy vulnerabilities as FreeBSD 9.0's kernel!Unfortunately, most kernel exploits cannot be triggered from the WebKit entry point that we currently have due to sandboxing constraints (likely to be just stock FreeBSD jails).And with FreeBSD 10 being out, it's unlikely that anyone is stashing away any private exploits for FreeBSD 9, so unless a new one is suddenly released, we're stuck with what is currently available.It may not be impossible to exploit the PS4 kernel by leveraging some of the existing kernel memory corruption vulnerabilities, but it certainly would't be easy.The best approach from here seems to be reverse engineering all of the modules which can be dumped, in order to document as many of Sony's custom system calls as possible; I have a hunch that we will have more luck targeting these, than the standard FreeBSD system calls.Recently Jaicrab has discovered two UART ports on the PS4 which shows us that there are hardware hackers interested in the PS4. Although the role of hardware hackers has traditionally been to dump the RAM of a system, like with the DSi, which we can already do thanks to the WebKit exploit, there's also the possibility of a hardware triggered kernel vulnerability being found, like geohot's original PS3 hypervisor hack. It remains most likely that a kernel exploit will be found on the PS4 through system call vulnerabilities though.ThanksflatzSKFUdroogieXerpiHungerTakezonasProximaSursa: http://cturt.github.io/ps4.html Edited August 21, 2015 by Nytro Quote