A research entry point for Jak X: Combat Racing (PS2 classic on PS5 — packages CUSA07842 (US) / CUSA07992 (EU), PS2 serial SCUS-97429), in the spirit of mast1c0re and Luac0re.
UltraC0re is a Jak X port/derivative of gezine's Luac0re — the original Lua framework this work is based on and modified.
[!IMPORTANT] Supported packages:
CUSA07842(US, disc release) andCUSA07992(EU).
Status
This repository is the result of reverse-engineering the Jak X emulator bundle from a save game up to user-controlled native code inside the PS5's PS2 emulator. The chain reached, in order:
- Save exploit — reversed the PS2 VMC save format (header/body/footer, CRC32, recomputed ECC) and used a profile-name overflow to take control of
$pcin the PS2 guest → arbitrary PS2 code execution (mast1c0re stage 1). - ps2emu escape — OOB through the emulator's SMAP/DEV9 (
SCMD/NCMD) registers → corruption ofgNStatusBuffer→ corruption of the IO-read dispatch table → native x86-64 RIP control in theeboot.binprocess. - Native primitives — base leaks (anti-ASLR for eboot/libc/libkernel), arbitrary read/write, direct syscalls, and RWX via
sceKernelJitCreate/Map/AliasSharedMemory. - Lua stage — the bootstrap hands control to the eboot's embedded Lua VM, running the UltraC0re Lua framework.
- JIT-compiler (
0x40) code execution + remote Lua loader — see below. - PS5 jailbreak payloads (experimental) — over the remote loader, this userland can host a Luac0re PS5 kernel-exploit payload with its exploit logic untouched (only a re-based gadget offset + a position-independent rebuild). So far only poops has been tried; in a successful run it reached kernel R/W and an on-console ELF/Lua loader. See Payloads.
How it works
The chain has two halves: a game-specific entry (Jak X only) and a generic stage that attacks the PS2 emulator itself and is therefore not tied to Jak X.
Part A — Entry (Jak X-specific)
- Save vulnerability. Jak X's PS2 save lives in a VMC (virtual memory card). The profile-name field overflows into an adjacent pointer the game later dereferences; a crafted name takes control of
$pcin the PS2 guest → arbitrary PS2 (MIPS r5900) code execution inside the emulated game (mast1c0re-style stage 1). The save bug is specific to Jak X (found by CelesteBlue). Delivery is in-place in the save body at0x71CAF0, with CRC32 + ECC recomputed (scripts/craft/craft_ultrac0re.py).
Everything below runs from that PS2 foothold and attacks the PlayStation PS2 emulator, not the game.
Part B — Emulator escape → JIT compiler (generic)
- Break out of the PS2 sandbox → native eboot. Driving the emulated SMAP/DEV9 network-adapter registers (
SCMD/NCMD) triggers an OOB that corruptsgNStatusBufferand then the native IO-read dispatch table, redirecting a function pointer to give native x86-64 RIP control in theeboot.binprocess (cr_caps 0x20) — mast1c0re stage 2. - Native primitives + Lua. Base leaks (anti-ASLR for eboot/libc/libkernel), arbitrary read/write, direct syscalls, and RWX via
sceKernelJit*. Control is then handed to the eboot's embedded Lua VM running the UltraC0re framework — from here on the exploit logic lives in Lua. - Cross the bridge into the JIT compiler (
0x40). The eboot cannot create network sockets (kernel cred-block:AF_INET/AF_INET6→ENOSYS), so the socket must come from the more-privileged JIT compiler processps2-emu-compiler(cr_caps 0x40), with the fd passed back. The two processes share a bridge (ps2_bridge_comm_rw). The bridge doorbellcmd 0x215gives an OOB write into the compiler's heap (itsSceLibcHeap), used in two steps against a VU0 object it keeps there: a firstcmd 0x215plants a fake vtable (and a fake stack) onto the object; a secondcmd 0x215carrying thejmp_vtableflag makes the compiler perform a virtual call (method70) through that fake vtable → native RIP inside the0x40process. - Drive the
0x40and get the socket. The hijacked call stack-pivots into a controlled ROP frame turned into a persistent spin-loop; the Lua side acts as a producer/consumer over the bridge, feeding calls/syscalls into the loop and reading their results — a remote-call primitive into the privileged process, with no further compilation needed. With it, asocket(AF_INET)is created with the compiler's credentials and handed to the eboot viaSCM_RIGHTS(fd-pass). The eboot then doesbind/listen/acceptnatively and runs whatever Lua is sent over the connection.
Why Part B is portable (the point). Part A — the save bug — is Jak X-specific and must be redone per game. Part B (steps 2–5) targets the PS2 emulator itself, not Jak X. That emulator — with its bridge and JIT compiler — is bundled inside each PS2-classic package (it ships together with the eboot and the disc image), so it is not part of the PS5 firmware and does not change with system updates. Other PS2 classics built on the same emulator share this exact route into the
0x40viacmd 0x215, needing little more than offset re-basing. This is the key difference from Luac0re, whose path targeted a much older emulator build present in only a handful of titles.
Portability check
scripts/check_portable.py <ps2-emu-compiler_dump.bin> scans a JIT-compiler dump for the exact signatures Part B relies on — the cmd 0x215 OOB-write doorbell and a hijackable method70 vcall on a heap object, plus the ROP gadget set — so you can flag which other PS2 titles run the same identical mechanism.
- PORTABLE — both the
cmd 0x215doorbell and themethod70target are present; the chain should apply after re-basing offsets. - PARTIAL — the
method70target is present but the specificcmd 0x215doorbell is not, so that title would need its own OOB primitive identified (e.g. SW Racer Revenge has themethod70target but uses a different doorbell — the one Luac0re drove).
python scripts/check_portable.py dumps/<game>_compiler.bin
Dump the ps2-emu-compiler process with ps5debug-NG (TCP 744). The result is structural — a positive verdict means the same vectors exist, not a guaranteed working exploit.
Payloads — running Luac0re's PS5 jailbreaks
lua/jitcompat.lua re-implements Luac0re's jit/* API on top of our cmd 0x215 engine, with the aim of letting a Luac0re native-shellcode payload run on this userland with its exploit logic unchanged. So far only poops has been tried: in a successful run it appeared to reach the full userland→kernel jailbreak and bring up the on-console ELF loader (:9021). This is not guaranteed — the kernel exploit itself is firmware-specific and probabilistic, so it may need a few attempts (or not work on a given build).
Porting a Luac0re payload needs only:
- one offset —
GADGET_OFFSET(thecall rbx; reteboot gadget) re-based to Jak X (0x19869); - a position-independent rebuild — the shellcode loads at a runtime address, so it must be RIP-relative (
-fpic).
No change to the exploit algorithm. Shellcode sources + build live in src/payloads/, prebuilt payloads in payloads/; send them over the :8888 loader.
Repository layout
lua/ UltraC0re Lua framework (derived from Luac0re):
main.lua entry point
door1.lua 0x40 code-exec (cmd 0x215) + remote_lua_loader
jitcompat.lua Luac0re jit/* API on our engine (payload compatibility)
global/rop/memory/func/misc/syscall.lua primitives
elf_jb/ ELF loader + kexp bin loaded by the jailbreak payloads
payloads/ prebuilt Luac0re PS5-jailbreak payloads (poops.lua, p2jb.lua)
src/ PS2 (MIPS r5900) bootstrap shellcode:
jakx_ultrac0re.c native bootstrap (eboot escape -> Lua VM handoff)
jakx_decoder.c encoded-delivery decoder
start.s, _link.ld, build.ps1
src/payloads/ payload shellcode sources + position-independent build (build_zig.sh)
scripts/craft/ VMC save (.card) crafting + ECC recompute
scripts/check_portable.py flag PS2 titles whose emulator uses the same exact mechanism
builds/ ships the prebuilt shellcode and saves/ the crafted save.
Build & run
- PS2 shellcode — ps2dev MIPS r5900 gcc; see
src/build.ps1(build.ps1 -src jakx_ultrac0re.c -addr 0x71CAF0 -out jakx_ultrac0re). - Save crafting —
scripts/craft/(VMC.card+ ECC recompute). Base the craft on a fresh VMC from the console. - Remote loader — once the exploit runs, the loader listens on port 8888; send a Lua payload over the socket to execute it.
- Jailbreak payloads — rebuild a Luac0re shellcode position-independent with zig:
cd src/payloads/<name> && bash build_zig.sh ../../../payloads/<name>.lua(compiles-fpicand re-injects the blob into the.lua). Send the resultingpayloads/<name>.luaover the:8888loader.
Running it
- Get the crafted save from the GitHub Releases section and re-sign the savedata for your own PSN account, then import it to the console — follow the resigning guide: https://github.com/n0llptr/remote_lua_loader/blob/main/SETUP.md. (The Lua framework is baked into the released save; nothing else to deploy.)
- Launch Jak X (
CUSA07842orCUSA07992EU), load the save, and enter Adventure mode. - When the cutscene starts, press △ (Triangle) to skip it and reach the trigger sooner — the exploit fires and the loader dialog appears (listening on
:8888).
Comments