232 lines
7.5 KiB
JavaScript
232 lines
7.5 KiB
JavaScript
|
|
/* Copyright (C) 2024-2025 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/>. */
|
||
|
|
|
||
|
|
// script for dumping libSceNKWebKit.sprx, libkernel_web.sprx, and
|
||
|
|
// libSceLibcInternal.sprx
|
||
|
|
|
||
|
|
// This script is for firmware 8.0x. You need to port this to your firmware if
|
||
|
|
// you want to use it. It only dumps the .text and PT_SCE_RELRO segments. It
|
||
|
|
// doesn't dump the entire ELF file.
|
||
|
|
//
|
||
|
|
// There's also some miscellaneous functions to dump functions from WebKit that
|
||
|
|
// were studied during the development of PSFree.
|
||
|
|
|
||
|
|
// libkernel libraries contain syscalls
|
||
|
|
// syscalls are enforced to be called from these libraries
|
||
|
|
//
|
||
|
|
// libkernel_web.sprx is used by the browser in firmwares >= 6.00
|
||
|
|
// libkernel.sprx for firmwares below
|
||
|
|
//
|
||
|
|
// libkernel_sys.sprx contains syscalls that aren't found in the others, such
|
||
|
|
// as mount and nmount
|
||
|
|
//
|
||
|
|
// the BD-J app uses libkernel_sys.sprx for example
|
||
|
|
|
||
|
|
// Porting HOWTO:
|
||
|
|
//
|
||
|
|
// You can only dump the WebKit module (libSceNKWebKit.sprx for FW >= 6.00,
|
||
|
|
// else libSceWebkit2.sprx) initially via dump_libwebkit() on any firmware.
|
||
|
|
// We'll use the WebKit dump to search for imported functions from libkernel
|
||
|
|
// and LibcInternal. Once we resolve the imports, we can use find_base() to get
|
||
|
|
// the boundaries of these modules.
|
||
|
|
//
|
||
|
|
// Most of the work is done for you at dump_lib*(). You just need to find the
|
||
|
|
// offset of the imported functions relative to WebKit's base address.
|
||
|
|
//
|
||
|
|
// import candidates:
|
||
|
|
//
|
||
|
|
// __stack_chk_fail() is a good import from libkernel to search for as it's
|
||
|
|
// easy to find since most functions are protected by a stack canary.
|
||
|
|
//
|
||
|
|
// For a LibcInternal import we searched for strlen() but you can search for
|
||
|
|
// any libc function such as memcpy().
|
||
|
|
|
||
|
|
import * as config from './config.mjs';
|
||
|
|
|
||
|
|
import { Int } from './module/int64.mjs';
|
||
|
|
import { Addr, mem } from './module/mem.mjs';
|
||
|
|
import { make_buffer, find_base, resolve_import } from './module/memtools.mjs';
|
||
|
|
import { KB, MB } from './module/offset.mjs';
|
||
|
|
|
||
|
|
import {
|
||
|
|
log,
|
||
|
|
align,
|
||
|
|
die,
|
||
|
|
send,
|
||
|
|
} from './module/utils.mjs';
|
||
|
|
|
||
|
|
import * as rw from './module/rw.mjs';
|
||
|
|
import * as o from './module/offset.mjs';
|
||
|
|
|
||
|
|
const origin = window.origin;
|
||
|
|
const port = '8000';
|
||
|
|
const url = `${origin}:${port}`;
|
||
|
|
|
||
|
|
const textarea = document.createElement('textarea');
|
||
|
|
// JSObject
|
||
|
|
const js_textarea = mem.addrof(textarea);
|
||
|
|
|
||
|
|
// boundaries of the .text + PT_SCE_RELRO portion of a module
|
||
|
|
function get_boundaries(leak) {
|
||
|
|
const lib_base = find_base(leak, true, true);
|
||
|
|
const lib_end = find_base(leak, false, false);
|
||
|
|
|
||
|
|
return [lib_base, lib_end]
|
||
|
|
}
|
||
|
|
|
||
|
|
// dump a module's .text and PT_SCE_RELRO segments only
|
||
|
|
function dump(name, lib_base, lib_end) {
|
||
|
|
// assumed size < 4GB
|
||
|
|
const lib_size = lib_end.sub(lib_base).lo;
|
||
|
|
log(`${name} base: ${lib_base}`);
|
||
|
|
log(`${name} size: ${lib_size}`);
|
||
|
|
const lib = make_buffer(
|
||
|
|
lib_base,
|
||
|
|
lib_size
|
||
|
|
);
|
||
|
|
send(
|
||
|
|
url,
|
||
|
|
lib,
|
||
|
|
`${name}.sprx.text_${lib_base}.bin`,
|
||
|
|
() => log(`${name} sent`)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// dump for libSceNKWebKit.sprx
|
||
|
|
function dump_libwebkit() {
|
||
|
|
let addr = js_textarea;
|
||
|
|
// WebCore::HTMLTextAreaElement
|
||
|
|
addr = addr.readp(0x18);
|
||
|
|
|
||
|
|
// vtable for WebCore::HTMLTextAreaElement
|
||
|
|
// in PT_SCE_RELRO segment (p_type = 0x6100_0010)
|
||
|
|
addr = addr.readp(0);
|
||
|
|
|
||
|
|
log(`vtable: ${addr}`);
|
||
|
|
const vtable = make_buffer(addr, 0x400);
|
||
|
|
send(url, vtable, `vtable_${addr}.bin`, () => log('vtable sent'));
|
||
|
|
|
||
|
|
const [lib_base, lib_end] = get_boundaries(addr);
|
||
|
|
dump('libSceNKWebKit', lib_base, lib_end);
|
||
|
|
|
||
|
|
return lib_base;
|
||
|
|
}
|
||
|
|
|
||
|
|
// dump for libkernel_web.sprx
|
||
|
|
function dump_libkernel(libwebkit_base) {
|
||
|
|
const offset = 0x8d8;
|
||
|
|
const vtable_p = js_textarea.readp(0x18).readp(0);
|
||
|
|
// __stack_chk_fail
|
||
|
|
const stack_chk_fail_import = libwebkit_base.add(offset);
|
||
|
|
|
||
|
|
const libkernel_leak = resolve_import(stack_chk_fail_import);
|
||
|
|
log(`__stack_chk_fail import: ${libkernel_leak}`);
|
||
|
|
|
||
|
|
const [lib_base, lib_end] = get_boundaries(libkernel_leak);
|
||
|
|
dump('libkernel_web', lib_base, lib_end);
|
||
|
|
}
|
||
|
|
|
||
|
|
// dump for libSceLibcInternal.sprx
|
||
|
|
function dump_libc(libwebkit_base) {
|
||
|
|
const offset = 0x918;
|
||
|
|
const vtable_p = js_textarea.readp(0x18).readp(0);
|
||
|
|
// strlen
|
||
|
|
const strlen_import = libwebkit_base.add(offset);
|
||
|
|
|
||
|
|
const libc_leak = resolve_import(strlen_import);
|
||
|
|
log(`strlen import: ${libc_leak}`);
|
||
|
|
|
||
|
|
const [lib_base, lib_end] = get_boundaries(libc_leak);
|
||
|
|
dump('libSceLibcInternal', lib_base, lib_end);
|
||
|
|
}
|
||
|
|
|
||
|
|
function dump_webkit() {
|
||
|
|
const libwebkit_base = dump_libwebkit();
|
||
|
|
dump_libkernel(libwebkit_base);
|
||
|
|
dump_libc(libwebkit_base);
|
||
|
|
}
|
||
|
|
|
||
|
|
// See globalFuncEval() from
|
||
|
|
// WebKit/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp at PS4
|
||
|
|
// 8.03.
|
||
|
|
//
|
||
|
|
// Used to dump the implementation of eval() to figure out the expression
|
||
|
|
// "execState.argument(0)".
|
||
|
|
//
|
||
|
|
// eval()'s native function receives a JSC::ExecState pointer (renamed to
|
||
|
|
// JSC::CallFrame on webkitgtk 2.34.4). That type has an argument() method
|
||
|
|
// which takes an index and returns the corresponding JSValue passed to eval(),
|
||
|
|
// e.g. execState.argument(0) is the first JSValue argument.
|
||
|
|
//
|
||
|
|
// execState.argument(0) evaluates to *(&execState + argumentOffset + 0).
|
||
|
|
// Knowing the argumentOffset is useful for passing data to ROP chains.
|
||
|
|
// argumentOffset is 0x30 for PS4 8.03.
|
||
|
|
//
|
||
|
|
// The PS4 uses the System V ABI. The ExecState pointer is passed to the rdi
|
||
|
|
// register since it is the first argument. ROP chains can get the JSValue
|
||
|
|
// passed via *(rdi + 0x30).
|
||
|
|
//
|
||
|
|
// For example, the expression "eval(1)" has the JSValue encoding of 1 passed
|
||
|
|
// to *(rdi + 0x30).
|
||
|
|
function dump_eval() {
|
||
|
|
let addr = js_textarea;
|
||
|
|
// WebCore::HTMLTextAreaElement
|
||
|
|
addr = addr.readp(0x18);
|
||
|
|
|
||
|
|
// vtable for WebCore::HTMLTextAreaElement
|
||
|
|
// in PT_SCE_RELRO segment (p_type = 0x6100_0010)
|
||
|
|
addr = addr.readp(0);
|
||
|
|
|
||
|
|
const libwebkit_base = find_base(addr, true, true);
|
||
|
|
const impl = mem.addrof(eval).readp(0x18).readp(0x38);
|
||
|
|
const offset = impl.sub(libwebkit_base);
|
||
|
|
send(
|
||
|
|
url,
|
||
|
|
make_buffer(impl, 0x800),
|
||
|
|
`eval_dump_offset_${offset}.bin`,
|
||
|
|
() => log('sent')
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initially we just used the vtable offset from pOOBs4 (0x1c8) and tested if
|
||
|
|
// it works. It did but let's add this dumper so we can verify it another way.
|
||
|
|
// See howto_code_exec.txt about code execution via the vtable of a textarea
|
||
|
|
// element.
|
||
|
|
function dump_scrollLeft() {
|
||
|
|
let proto = Object.getPrototypeOf(textarea);
|
||
|
|
proto = Object.getPrototypeOf(proto);
|
||
|
|
proto = Object.getPrototypeOf(proto);
|
||
|
|
|
||
|
|
const scrollLeft_get =
|
||
|
|
Object.getOwnPropertyDescriptors(proto).scrollLeft.get
|
||
|
|
;
|
||
|
|
|
||
|
|
// get the JSCustomGetterSetterFunction
|
||
|
|
const js_func = mem.addrof(scrollLeft_get);
|
||
|
|
const getterSetter = js_func.readp(0x28);
|
||
|
|
const getter = getterSetter.readp(8);
|
||
|
|
|
||
|
|
const libwebkit_base = find_base(getter, true, true);
|
||
|
|
const offset = getter.sub(libwebkit_base);
|
||
|
|
send(
|
||
|
|
url,
|
||
|
|
make_buffer(getter, 0x800),
|
||
|
|
`scrollLeft_getter_dump_offset_${offset}.bin`,
|
||
|
|
() => log('sent')
|
||
|
|
);
|
||
|
|
}
|