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) and CUSA07992 (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:

  1. Save exploit — reversed the PS2 VMC save format (header/body/footer, CRC32, recomputed ECC) and used a profile-name overflow to take control of $pc in the PS2 guest → arbitrary PS2 code execution (mast1c0re stage 1).
  2. ps2emu escape — OOB through the emulator's SMAP/DEV9 (SCMD/NCMD) registers → corruption of gNStatusBuffer → corruption of the IO-read dispatch table → native x86-64 RIP control in the eboot.bin process.
  3. Native primitives — base leaks (anti-ASLR for eboot/libc/libkernel), arbitrary read/write, direct syscalls, and RWX via sceKernelJitCreate/Map/AliasSharedMemory.
  4. Lua stage — the bootstrap hands control to the eboot's embedded Lua VM, running the UltraC0re Lua framework.
  5. JIT-compiler (0x40) code execution + remote Lua loader — see below.
  6. 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)

  1. 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 $pc in 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 at 0x71CAF0, 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)

  1. Break out of the PS2 sandbox → native eboot. Driving the emulated SMAP/DEV9 network-adapter registers (SCMD/NCMD) triggers an OOB that corrupts gNStatusBuffer and then the native IO-read dispatch table, redirecting a function pointer to give native x86-64 RIP control in the eboot.bin process (cr_caps 0x20) — mast1c0re stage 2.
  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.
  3. Cross the bridge into the JIT compiler (0x40). The eboot cannot create network sockets (kernel cred-block: AF_INET/AF_INET6ENOSYS), so the socket must come from the more-privileged JIT compiler process ps2-emu-compiler (cr_caps 0x40), with the fd passed back. The two processes share a bridge (ps2_bridge_comm_rw). The bridge doorbell cmd 0x215 gives an OOB write into the compiler's heap (its SceLibcHeap), used in two steps against a VU0 object it keeps there: a first cmd 0x215 plants a fake vtable (and a fake stack) onto the object; a second cmd 0x215 carrying the jmp_vtable flag makes the compiler perform a virtual call (method70) through that fake vtable → native RIP inside the 0x40 process.
  4. Drive the 0x40 and 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, a socket(AF_INET) is created with the compiler's credentials and handed to the eboot via SCM_RIGHTS (fd-pass). The eboot then does bind/listen/accept natively 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 0x40 via cmd 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 0x215 doorbell and the method70 target are present; the chain should apply after re-basing offsets.
  • PARTIAL — the method70 target is present but the specific cmd 0x215 doorbell is not, so that title would need its own OOB primitive identified (e.g. SW Racer Revenge has the method70 target 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:

  1. one offsetGADGET_OFFSET (the call rbx; ret eboot gadget) re-based to Jak X (0x19869);
  2. 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 craftingscripts/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 -fpic and re-injects the blob into the .lua). Send the resulting payloads/<name>.lua over the :8888 loader.

Running it

  1. 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.)
  2. Launch Jak X (CUSA07842 or CUSA07992 EU), load the save, and enter Adventure mode.
  3. 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).