Since no public statements about the PS4 hacking have been received for a long time, it is time to break the silence and tell a little about how far the progress has been with regard to the PS4 hacking, as well as about the reasons that prevent further progress.
In this article, I will touch on some of the security principles that apply to all modern systems, as well as share my findings made through the implementation of
ROP tests on my PS4.
If you are not familiar with the use of exploits, you should first read
my last article on hacking DS games using stack integrity vulnerabilities in save files.
')
Download everything you need for your own experiments
here , currently only firmware 1.76 is supported.
PS4 Known Facts
As you most likely know, the PS4 uses a special eight-core x86-64 CPU from AMD, about the architecture of which quite a lot of research has been published, and even if this particular version of the processor is slightly different from the generally accepted standard, it will hardly be noticeable. For example, PFLA ( Page Fault Liberation Army ) at 29C3 ( 29th Chaos Communication Congress ) showed proof of a proof-of-concept that you can implement a full Turing machine using only page faults ( page fault ) and x86 MMU, the video is available on YouTube . It will be interesting to those who, having run the code in a virtual machine, at the same time want to execute instructions on the host CPU.
EurAsia news article numbered 3251
And we are dealing not only with a well-documented CPU architecture - the
software used in
PS4 mostly belongs 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 major 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 widespread use and maturity of the project, WebKit is not devoid of individual vulnerabilities; You can learn about most of them from the
Pwn2Own entries.
In particular, the browser in PS4 with firmware 1.76 uses a version of WebKit vulnerable to
CVE-2012-3748 , a buffer overflow in the
heap-based buffer overflow data area in the
JSArray::sort(...)
method.
In 2014, nas and Proxima stated that they were able to
successfully port this exploit for use on the PS4 browser, and put the PoC code in public, which initiated the PS4 hacking process.
This code gives 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 establish control over the instruction counter (for ROP).
Since then,
many other vulnerabilities have been discovered
in WebKit , which presumably allow dumping modules and ROPs 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, the PS4 uses a kernel that controls the options of different memory areas. Memory pages that are marked as executable cannot be overwritten; pages marked as recordable cannot 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 the memory and its subsequent execution. However, we can execute code that is already loaded into memory and marked as executable.
By itself, the possibility of jumping to the same address does not bear any particular benefit if we cannot write our own code at this address - that is why we will resort to ROP.
Return-oriented programming (ROP) is just an enhanced version of the traditional “stack smashing” (buffer overflow attack), but instead of overwriting a single value that a PC will jump to, we can link together many different addresses known as “gadgets”
Usually, a gadget is just the only desired construction, followed by
ret
.
In x86_64 assembler, when execution reaches the
ret
instruction, the 64-bit value is pushed out of the stack and the PC jumps onto it; since we can control the stack, we can force each
ret
instruction to jump to the next necessary gadget.
For example, starting from
0x80000
instructions can be stored:
mov rax, 0 ret
And starting from
0x90000
following instructions are stored:
mov rbx, 0 ret
If we rewrite the return address on the stack so that it
0x80000
and then
0x90000
, then as soon as the execution reaches the first
ret
instruction, it will jump to
mov rax, 0
, and immediately after this the next
ret
will
0x90000
from the stack
0x90000
and jump to
mov rbx, 0
.
Thus, this chain will play into our hands and set both the
rax
and
rbx
to 0, as if we simply wrote the code in one place and executed it sequentially.
ROP chains are not limited to a list of addresses; Suppose that the following instructions go from
0xa0000
:
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 also do not need to end on
ret
instructions; we can use
jmp
terminated gadgets:
add rax, 8 jmp rcx
By making
rcx
point to a
ret
instruction, the chain will execute 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, by 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 have to set
r9
to some dummy value:
pop r8 pop r9 ret
Although from time to time you will have to show your creative abilities when writing ROP chains, nevertheless, it is considered that using a sufficiently large code dump of the gadgets will be sufficient for full Turing functionality; this makes ROP a viable DEP bypass method.
Gadget Search
The following metaphor will help you understand ROP.
Imagine that you are writing a new chapter in a book, while using only those words that stood at the end of the sentences in previous chapters. Obviously, by virtue of the principles of constructing phrases, you can hardly find the words “and” or “or” at the end of one of the sentences — but we will need these connecting elements if we want to write something meaningful.
It is possible, however, that one of the sentences ended on the word "sand". And, although according to the author's intention, we must read this word entirely starting with the letter “s”, if we begin our reading with “a”, then by
pure chance we will get a completely different word - “and”, which is what we needed.
These principles also apply to ROP.
Since the structure of almost all functions looks like this:
; push rbp mov rbp, rsp push r15 push r14 push r13 push r12 push rbx sub rsp, 18h ; ; add rsp, 18h pop rbx pop r12 pop r13 pop r14 pop r15 pop rbp ret
Therefore, we should expect the discovery of only
pop
gadgets, or, more rarely,
xor rax, rax
, which set the value to 0 before returning.
Comparison like
cmp [rax], r12 ret
does not make any sense, since the result of the comparison is not used by the function. However, the likelihood of finding such gadgets still remains.
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 begins.
The x86_64 architecture is a variable-length CISC instruction set. 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 x86_64 instruction set.
- Wikipedia
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 from
0x52be14
:
000000000052BE14 cmp [rax], r12 000000000052BE17 ret
Even if this code was never intended to be executed, it is located in the memory area marked “executable”, which makes it very attractive for use as a gadget.
Of course, it would be incredibly costly to spend time searching for all possible ways of interpreting the code before each
ret
instruction manually; existing utilities can do this for us. To search for ROP gadgets, I prefer to use
rp ++ ; to generate a text file filled with gadgets, just enter the command:
rp-win-x64 -f mod14.bin
Segmentation errors
If we try to execute a non-executable page of memory, or try to write to a non-writable page of memory, a segmentation error will occur.
For example, this is an attempt to execute code on a stack that is read-only and write-only (rw):
setU8to(chain.data + 0, 0xeb); setU8to(chain.data + 1, 0xfe); chain.add(chain.data);
And so - an attempt to write code that is "mamapped" only for reading and execution (rx):
setU8to(moduleBases[webkit], 0)
If a segmentation error occurs, the message “Not enough free system memory” appears on the screen, and the page will not load:
The reason for the output of this message may be something else - for example, the execution of a wrong instruction or an unrealized system call - but most often it gets out 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 process address space, namely, the image of the executable file, loadable libraries, heaps and stacks. Because of her, the base addresses of the modules change each time you launch your PS4.
I received evidence that
in the oldest versions of the firmware (1.05), the ASLR was disabled , but it appeared somewhere around 1.70. Note that the ASLR is disabled for the kernel, at least for firmware versions 1.76 and below, and this will be proved further.
For most exploits, ASLR will be a problem, because if you don’t know the addresses of gadgets in memory, you won’t guess what you need 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 executing the ROP chain, bypassing the ASLR.
The module table also includes the module file names:
- 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
Despite the fact that the PS4 mostly uses the [Signed] PPU Relocatable Executable ([S] PRX) format for modules, lines that refer to the [Signed] Executable and Linking Format ([S] ELF) object files are seen in the libSceSysmodule.sprx dump. ) - 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 only those that are loaded by the browser) is available in
libSceSysmodule.sprx
. We can download and dump some of them thanks to several special system calls for Sony authorship, 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 the ASLR bypass, we can read the browser user agent, and substitute another ROP chain for another browser version, giving our exploit the highest possible compatibility level.
We can even use javascript to read the memory of our gadget addresses in order to make sure they are correct, which gives us almost perfect reliability.
Dynamic writing of ROP chains acquires meaning compared to their preliminary generation by the script.
For these reasons, I created my own JavaScript framework for writing ROP chains,
JuSt-ROP .
JavaScript pitfalls
JavaScript uses the double-precision (64-bit)
IEEE-754 representation of numbers. This gives us 53 bits of precision (the VT_R8 mantissa has only 53 bits), which means that it is impossible to display every 64-bit value — for some of them, you will have to apply an approximation.
If you just need to set a 64-bit number to some small value, like
256
, then
setU64to
will cope with the task. But for cases when 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 must write data in 32-bit blocks (remembering that the PS4 uses little-endian order) to make sure that each byte is identical.
System calls
Interestingly, PS4 uses the same call format as Linux and MS-DOS for system calls, with arguments stored in registers rather than the traditional UNIX method (which FreeBSD uses by default) when the arguments are stored on the stack:
Register | Value |
---|
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 make 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"); }
Using 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 can potentially perform a kernel exploit.
If you reverse engineer the modules to identify some of the special Sony system calls, you can find 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 simple 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; }
Using this approach, they can perform any system call from C.
When writing ROP chains, we can use the following convention:
// ID : chain.syscall("getpid", 20); chain.syscall("getpid", 0, 20);
This is useful to remember if you can choose the most convenient of the available gadgets.
getpid
A single system call number
20
,
getpid(void)
, is already able to tell us a lot about the kernel.
The very fact that this system call works tells us that Sony did not even bother to mix the system call numbers, as required by the
security through obscurity technique (and under the BSD license they could do it without publishing new system numbers on the Internet calls).
Thus, we automatically got our hands on a
list of system calls that you can try to make in the PS4 core.
Second, by calling
getpid()
, restarting the browser, and then calling it again, we will get a return value 1 greater than the previous one. Although FreeBSD has been supporting
PID randomization since version 4.0, consistent PID allocation is the default behavior. Apparently, Sony did not bother to strengthen the protection here, like it was done in projects like
HardenedBSD .
How many system calls are there?
The last system call on FreeBSD 9 is
wait6
number
523
; all that has the number above - special system calls Sony.
Attempting to call any of the special Sony system calls without valid arguments will return error
0x16
,
"Invalid argument"
; however, any compatible system calls, or unrealized system calls, will result in the error
"There is not enough free system memory"
.
Through trial and error, I found out that the system call number
617
is the last call by Sony, all calls are not implemented further.
Based on this, we can make a logical conclusion that in the PS4 core there are 85 special system calls (617-532) authored by Sony.
This is significantly less than in the PS3, in which there were almost 1000 system calls in general. Well, even if this indicates less room for potential attack vectors, it will be easier for us to document all the challenges.
We go further. 9 of these 85 system calls always return 0x4e, ENOSYS, which means a simple thing - these calls work only on test devices for developers, leaving us with just 76 useful calls.
Of these 76, libkernel.sprx refers only 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 there are wrappers for them in libkernel.sprx), some of the remaining 31 are still accessible from the browser process. It is possible that in these unintentionally abandoned calls the likelihood of finding a vulnerability is much higher, since it took the least time to test them.
libkernel.sprx
In order to understand how special system 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 an excerpt of the
_libpthread_init
code from the
thr_init.c
file:
/* * 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 code above 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 dumps of modules for analyzing system calls
libkernel is not fully open: it includes a large number of Sony’s own code that could uncover their system calls.
Although the analysis process will differ depending on the selected system call, for some of them it is quite easy to figure out the composition of the arguments that are passed to the call.
The system call wrapper 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 available for all system call wrappers, and even for those that do not take 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
A good idea would be to look for a few more pieces, just to make sure that the registers were not 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
, (rdi, rsi, rdx), , .
, JuSt-ROP:
chain.syscall("unknown", 601, 0x10, 0, 1); chain.syscall("unknown", 601, 9, 0, 0);
, 0 , ,
jz
test
.
- , , , .
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 are forced to analyze them blindly.
If we assume that a particular system call can take a specific set of arguments, then we can brute force all system calls that return a specific value (0 for success) with the selected arguments, and ignore all those that returned an error.
We can also transfer zeros for all arguments, and brute force, all system calls that return helpful error like 0xe
, "Bad address"
that indicate that the call is received at least one pointer.
-, ROP- .
onload
body
:
<body onload="exploit()">
HTTP GET. JavaScript, 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);
, , , :
if(chain.getVariable(0) == 0x16) window.location.assign("index.php?b=" + (<?php print($_GET["b"]); ?> + 1).toString());
?b=0 Sony.
, , , .
538
As an example, let's look at system call 538 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 increases by 1.
Other potential arguments that you can try to substitute are the PID, the thread ID, and the file descriptor.
Despite the fact that most system calls return 0 when successful, some calls return an incrementing value with each new call — apparently, these calls allocate a resource, like a file descriptor.
The next step is to monitor the data before and after making a system call in order to understand whether anything has been recorded in it.
Since there are no changes in the data, we can assume with a clear conscience that this is input.
Then try to feed the method a long string as the first argument. You should try this with every input that you can detect, since there is a possibility of detecting a buffer overflow.
writeString(chain.data, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);
We obtain as the value of the return 0x3f
, ENAMETOOLONG
. Alas, but we see that the system call correctly restricts the name (32 bytes including the terminator NULL
), but now we know that the method expects a string, not a structure.
Well, now we have a few ideas on what this challenge can do. The most obvious option is some kind of action related to the file system (for example, a special version mkdir
or open
), but this version is unlikely to suit us - because the resource is allocated before we write any data to the index.
Let's try to check if the first parameter is a path. We split it up into several characters /
and see if it will allow a long string to be passed 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 a path; this is the name for something that will be placed in memory and will get a sequential identifier .
After analyzing other system calls, I was able to find that all of the following have the same behavior:
Using the information received, it is almost impossible to guess what these system calls are doing, but if you run other tests, you will gradually uncover the mystery. I will save you some time - system call 538 allocates memory for the event flag (and takes not only the name as a parameter).
With basic knowledge of how the kernel works, you can guess and then check what memory is allocated for system calls — semaphores, mutexts, and so on.
Dump additional modules
We can dump additional modules as follows:
- Load the module
- Get the base address of the module
- Dump the module.
I took on the tedious work of loading and dumping every possible module from the browser with my hands and published the results on psdevwiki . All modules with a “Yes” marker can be dumped by this method.
To load the module, we need to use the function sceSysmoduleLoadModule
from libSceSysmodule.sprx + 0x1850
. The first parameter is the identifier of the loadable module, the other three simply pass 0.
The following JuSt-ROP method is useful for making 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, for loading we libSceAvSetting.sprx (0xb)
use:
chain.call("sceSysmoduleLoadModule", libSysmodule, 0x1850, 0xb, 0, 0, 0);
Like most system calls, this one should return 0 if successful. To see the identifier of the module allocated in memory, we can use one of the Sony system calls at number 592 to get the list of loaded modules:
var countAddress = chain.data; var modulesAddress = chain.data + 8; // 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 other optional 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 will see an additional element, 0x65. Remember - the module identifier is not the same as the identifier of the loaded module.
Now we can use another Sony system call number 593, which takes the identifier of the loaded module and the buffer, and fills the buffer with information about the loaded module, including its base address. Since the identifier of the loaded module is always 0x65, we can “hard-code” it in our chain, instead of storing the result from the list of modules.
The buffer must start with a structure size to be returned, otherwise, it returns an error 0x16
, "Invalid argument"
:
setU64to(moduleInfoAddress, 0x160); chain.syscall("getModuleInfo", 593, 0x65, moduleInfoAddress); chain.execute(function() { logAdd(hexDump(moduleInfoAddress, 0x160)); });
0, , :
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);
!
dump(codeBase, codeSize + dataSize)
Sony, 608, 593 , :
setU64to(moduleInfoAddress, 0x1a8)
, .
PS4 FreeBSD 9.0.
,
/dev/
, — ,
/
— .
, ,
gendents
read
, :
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);
:
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 will /dev/urandom
fill the memory with random data.
You can also parse this memory and get a list of entities; Take a look at browser.html
from the repository, which serves as a file manager:
Alas, because of the sandbox, we do not have full access to the file system. Attempt to read files or directories that exist , but access to them is limited, you will return error 2 ENOENT
, "No such file or directory"
. True, we can still get access to various interesting features - encrypted saving files, trophies and account information - I will tell you more about them in my next articles.
Sandbox
— , .
, 1,
EPERM
,
"Operation not permitted"
;
ptrace
, .
. ,
mmap
,
477 ,
71 197 ; , .
,
exit
, (
segmentation fault ):
chain.syscall("exit", 1, 0);
SCTP-
0x2b
,
EPROTONOSUPPORT
, , SCTP- PS4:
//int socket(int domain, int type, int protocol); //socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP); chain.syscall("socket", 97, 2, 1, 132);
,
mmap
PROT_READ | PROT_WRITE | PROT_EXEC
,
PROT_EXEC
. 3 (RW), :
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");
open source , PS4 ,
Capsicum , PS4 «»
jails FreeBSD , ( ).
Jail
jail- FreeBSD PS4 auditon, jailed-:
chain.syscall("auditon", 446, 0, 0, 0);
,
audition
—
jailed
, , ENOSYS:
if (jailed(td->td_ucred)) return (ENOSYS)
,
EPERM
mac_system_check_auditon
:
error = mac_system_check_auditon(td->td_ucred, uap->cmd); if (error) return (error);
priv_check
:
error = priv_check(td, PRIV_AUDIT_CONTROL); if (error) return (error);
, ,
priv_check
,
,
EINVAL
- , 0:
if ((uap->length <= 0) || (uap->length > sizeof(union auditon_udata))) return (EINVAL)
mac_system_check_auditon
priv_check
ENOSYS
,
jailed
— ,
ENOSYS
.
,
ENOSYS
(
0x48
).
, , PS4, jail-,
jailed
.
FreeBSD 9.0
There is not much point in searching for new vulnerabilities in the source code of the FreeBSD 9.0 kernel , since since its release in 2012, several kernel exploits have been found , to which PS4 could be potentially vulnerable.
We can drop some of them at once:
FreeBSD 9.0-9.1 mmap / ptrace - Privilege Escalation Exploit - will not work, because we do not have access to the system call ptrace
.
FreeBSD 9.0 - Intel SYSRET Kernel Privilege Escalation Exploit - will not work, because the PS4 uses an AMD processor.
FreeBSD Kernel - Multiple Vulnerabilities — , , SCTP, PS4 , .
,
, - .
getlogin
, —
getlogin .
getlogin , , - , , . , , .
, (49)
int getlogin_r(char *name, int len);
,
char *getlogin(void);
.
, :
chain.syscall("getlogin", 49, chain.data, 17);
, 17 , :
The username length is limited to MAXLOGNAME (from <sys / param.h>) characters, currently 17 characters, including empty ones.
- FreeBSD Man Pages
After executing the chain, the return value is 0, which means that the system call worked! Great start. Now let's take a look at the memory we pointed out:
Before the execution of 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 runs from the root! This is a surprise.
But what is even more interesting is that the leaked memory resembles a pointer to something in the core, that every time the chain starts, it remains the same; This evidence confirms Yifanlu’s theory that there is no ASLR protection ( address space randomization ) at the kernel level in PS4 !
Total
, PS4 FreeBSD 9.0. , , . Sony , , , .
, PS4 «» , FreeBSD 9.0!
, WebKit ( jails FreeBSD). , FreeBSD 9 , , , . , PS4 , .
- , , Sony; , , FreeBSD.
Jaicrab PS4 (UART) , hardware- . hardware- RAM (
DSi ), WebKit — , , «» ,
PS3 geohot . , , PS4 .
!
, /, .