From 7ac46bed77e5fe34d3eeb901360bf47be28f5a74 Mon Sep 17 00:00:00 2001 From: Collins3560 Date: Mon, 10 Aug 2026 02:28:05 +0300 Subject: [PATCH] Cinebreak Suite v1: CLI, network shell payload, kernel offset scanner, CI - cinebreak: unified ops CLI (build/find/chain/payload new/status/update) - payloads/cinebreak-shell: remote kernel R/W shell (TCP :9026) for post-chain ops - tools/kernel-offsets.py: pattern scanner for the 5 Poopsploit kernel offsets + selftest - .github/workflows/build.yml: auto ISO build on push + weekly upstream tracker - README: suite docs --- .github/workflows/build.yml | 35 +++++ README.md | 19 +++ cinebreak | 93 ++++++++++++++ payloads/cinebreak-shell/Makefile | 20 +++ payloads/cinebreak-shell/manifest.txt | 2 + .../src/org/bdj/external/CinebreakShell.java | 90 +++++++++++++ tools/kernel-offsets.py | 121 ++++++++++++++++++ 7 files changed, 380 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100755 cinebreak create mode 100644 payloads/cinebreak-shell/Makefile create mode 100644 payloads/cinebreak-shell/manifest.txt create mode 100644 payloads/cinebreak-shell/src/org/bdj/external/CinebreakShell.java create mode 100755 tools/kernel-offsets.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6690f5f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,35 @@ +name: build-iso +on: + push: { branches: [master] } + workflow_dispatch: + schedule: [{ cron: "0 3 * * 1" }] # weekly: track upstream offsets + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install JDK 8 + run: | + curl -sL -o jdk8.tar.gz "https://api.adoptium.net/v3/binary/latest/8/ga/linux/x64/jdk/hotspot/normal/eclipse" + tar xzf jdk8.tar.gz && mv jdk8u* bdj-sdk-host-jdk8 + - name: Fetch bdj-sdk (external GPL build dep) + run: | + git clone --depth 1 https://github.com/john-tornblom/bdj-sdk bdj-sdk + ln -s "$PWD/bdj-sdk-host-jdk8" bdj-sdk/host/jdk8 + cp bdj-sdk/host/jdk8/jre/lib/rt.jar bdj-sdk/target/lib/rt.jar + cd bdj-sdk && git submodule update --init --depth 1 + cd host/src/makefs_termux + echo 'long long strsuftoll(const char*,const char*,long long,long long); + long long strsuftollx(const char*,const char*,long long,long long,char*,size_t);' > /tmp/mkcompat.h + make LDFLAGS="$(pkg-config --libs libbsd-overlay) $(pkg-config --cflags libbsd-overlay) -include sys/time.h -include time.h -include /tmp/mkcompat.h -D_DEFAULT_SOURCE" + make install DESTDIR="$PWD/../../.." + - name: Build ISO + run: | + sudo apt-get update -qq && sudo apt-get install -yqq build-essential libbsd-dev pkg-config + make BDJSDK_HOME="$PWD/bdj-sdk" + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: cinebreak-iso + path: BD-JB5-2.0.iso diff --git a/README.md b/README.md index 23ddcdc..133c5ee 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,25 @@ A **one-shot, chainable PS5 jailbreak** built on [Gezine's BD-JB5](https://githu | `tools/` | Local console simulators for testing without hardware | | `CHAIN.md` | Every link of the chain mapped and documented | + +## 🧰 Cinebreak Suite (v1) + +| Tool | What it does | +|---|---| +| `cinebreak` | Unified CLI: `build` · `find` · `chain` · `payload new ` · `status` · `update` | +| `payloads/cinebreak-shell/` | **Network command shell** — after the chain lands, `nc 9026` gives kernel read/write, memory dumps, notifications | +| `tools/kernel-offsets.py` | Pattern scanner for the 5 kernel offsets Poopsploit needs — future firmware drops don't break the chain (`selftest` included) | +| `.github/workflows/build.yml` | CI: auto-builds the ISO on push + weekly (tracks upstream offset changes) | + +**Shell quickstart:** +```bash +./cinebreak chain --elf # or push cinebreak-shell.jar +nc 9026 +> kread 0xffffffff... # kernel read32 +> kdump 64 # hex dump +> notify hello # PS5 notification +``` + ## 🔨 Build the ISO ```bash diff --git a/cinebreak b/cinebreak new file mode 100755 index 0000000..c1899aa --- /dev/null +++ b/cinebreak @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Cinebreak Suite CLI - one tool to rule the whole chain.""" +import argparse, os, subprocess, sys, time, socket + +ROOT = os.path.dirname(os.path.abspath(__file__)) + +def sh(cmd, **kw): + r = subprocess.run(cmd, shell=True, text=True, capture_output=True, **kw) + if r.stdout: print(r.stdout, end="") + if r.returncode != 0 and r.stderr: print(r.stderr, end="", file=sys.stderr) + return r.returncode + +def cmd_build(args): + print("[*] building ISO (Cinebreak engine)") + rc = sh(f"bash {ROOT}/build.sh {args.sdk}") + print("[+] build done" if rc == 0 else "[-] build failed") + +def cmd_find(args): + sys.path.insert(0, ROOT) + from ps5find import find_ps5, PORTS + ip = find_ps5(args.net) + if ip: print(f"[+] console: {ip}") + +def cmd_chain(args): + sys.path.insert(0, ROOT) + from ps5chain import chain, JAR_PATH + chain(args.ip, args.elf) + +def cmd_payload_new(args): + name = args.name + pdir = os.path.join(ROOT, "payloads", name) + if os.path.exists(pdir): sys.exit("[-] payload exists") + os.makedirs(f"{pdir}/src/org/bdj/external") + open(f"{pdir}/manifest.txt", "w").write( + f"Manifest-Version: 1.0\nMain-Class: org.bdj.external.{name.capitalize()}\n") + open(f"{pdir}/Makefile", "w").write( + "JAR_NAME := %s.jar\n\nMAKEFILE_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))\n" + "BDJSDK_HOME ?= $(MAKEFILE_DIR)/../../../..\nJAVA8_HOME ?= $(BDJSDK_HOME)/host/jdk8\n" + "JAVAC := $(JAVA8_HOME)/bin/javac\nJAR := $(JAVA8_HOME)/bin/jar\n\n" + "CLASSPATH := $(BDJSDK_HOME)/target/lib/enhanced-stubs.zip:$(BDJSDK_HOME)/target/lib/rt.jar:../../discdir/BDMV/JAR/00000.jar\n" + "SOURCES := $(wildcard src/org/bdj/external/*.java)\n" + "JFLAGS := -Xlint:-options -source 1.4 -target 1.4\n\n" + "all: $(JAR_NAME)\n\n$(JAR_NAME): $(SOURCES) manifest.txt\n" + "\t$(JAVAC) $(JFLAGS) -cp $(CLASSPATH) $(SOURCES)\n" + "\t$(JAR) cfm $@ manifest.txt -C src/ .\n\nclean:\n" + "\trm -f src/org/bdj/external/*.class $(JAR_NAME)\n" % name) + tmpl = f'''package org.bdj.external; + +import org.bdj.Status; + +public class {name.capitalize()} {{ + public static void main(String[] args) {{ + Status.println("=== {name} payload ==="); + Status.println("hello from Cinebreak"); + }} +}} +''' + open(f"{pdir}/src/org/bdj/external/{name.capitalize()}.java", "w").write(tmpl) + print(f"[+] scaffolded payloads/{name}") + rc = sh(f"make -C {pdir} BDJSDK_HOME={args.sdk}") + print("[+] compiled" if rc == 0 else "[-] compile failed (need built discdir + sdk)") + +def cmd_status(args): + print("[*] Cinebreak status") + for name in ("ps5chain.py", "ps5find.py", "cinebreak", "build.sh", "CHAIN.md"): + p = os.path.join(ROOT, name) + print(f" {'OK ' if os.path.exists(p) else 'MISS'} {name}") + built = os.path.exists(os.path.join(ROOT, "discdir/BDMV/JAR/00000.jar")) + print(f" {'OK ' if built else 'NO '} engine jar (run 'cinebreak build' first)") + payloads = [d for d in os.listdir(os.path.join(ROOT, "payloads")) + if os.path.isdir(os.path.join(ROOT, "payloads", d)) and not d.startswith(".")] + print(f" payloads: {', '.join(payloads)}") + +def cmd_update(args): + print("[*] checking Gezine/BD-JB5 for updates...") + rc = sh("git ls-remote https://github.com/Gezine/BD-JB5.git HEAD | cut -c1-12") + print(f"[+] upstream HEAD: {rc and rc or ''}" if isinstance(rc, str) else "[-] fetch failed") + +def main(): + p = argparse.ArgumentParser(prog="cinebreak", description="Cinebreak Suite - PS5 BD-J chain ops") + sub = p.add_subparsers(dest="cmd") + b = sub.add_parser("build", help="build the ISO"); b.add_argument("sdk", nargs="?", default=os.path.expanduser("~/ps5dev/bdj-sdk")) + f = sub.add_parser("find", help="find console on LAN"); f.add_argument("net", default="192.168.1.0/24") + c = sub.add_parser("chain", help="run full chain"); c.add_argument("ip"); c.add_argument("--elf") + pn = sub.add_parser("payload", help="scaffold + compile a new payload"); pn.add_argument("name"); pn.add_argument("sdk", nargs="?", default=os.path.expanduser("~/ps5dev/bdj-sdk")) + sub.add_parser("status", help="kitchen check") + sub.add_parser("update", help="check upstream") + a = p.parse_args() + {"build": cmd_build, "find": cmd_find, "chain": cmd_chain, "payload": cmd_payload_new, + "status": cmd_status, "update": cmd_update}.get(a.cmd, lambda x: p.print_help())(a) + +if __name__ == "__main__": + main() diff --git a/payloads/cinebreak-shell/Makefile b/payloads/cinebreak-shell/Makefile new file mode 100644 index 0000000..dd2a838 --- /dev/null +++ b/payloads/cinebreak-shell/Makefile @@ -0,0 +1,20 @@ +JAR_NAME := cinebreak-shell.jar + +MAKEFILE_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +BDJSDK_HOME ?= $(MAKEFILE_DIR)/../../../.. +JAVA8_HOME ?= $(BDJSDK_HOME)/host/jdk8 +JAVAC := $(JAVA8_HOME)/bin/javac +JAR := $(JAVA8_HOME)/bin/jar + +CLASSPATH := $(BDJSDK_HOME)/target/lib/enhanced-stubs.zip:$(BDJSDK_HOME)/target/lib/rt.jar:../../discdir/BDMV/JAR/00000.jar +SOURCES := $(wildcard src/org/bdj/external/*.java) +JFLAGS := -Xlint:-options -source 1.4 -target 1.4 + +all: $(JAR_NAME) + +$(JAR_NAME): $(SOURCES) manifest.txt + $(JAVAC) $(JFLAGS) -cp $(CLASSPATH) $(SOURCES) + $(JAR) cfm $@ manifest.txt -C src/ . + +clean: + rm -f src/org/bdj/external/*.class $(JAR_NAME) diff --git a/payloads/cinebreak-shell/manifest.txt b/payloads/cinebreak-shell/manifest.txt new file mode 100644 index 0000000..acbf4ef --- /dev/null +++ b/payloads/cinebreak-shell/manifest.txt @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Main-Class: org.bdj.external.CinebreakShell diff --git a/payloads/cinebreak-shell/src/org/bdj/external/CinebreakShell.java b/payloads/cinebreak-shell/src/org/bdj/external/CinebreakShell.java new file mode 100644 index 0000000..49a8eff --- /dev/null +++ b/payloads/cinebreak-shell/src/org/bdj/external/CinebreakShell.java @@ -0,0 +1,90 @@ +package org.bdj.external; + +import java.io.*; +import java.net.*; +import org.bdj.Status; +import org.bdj.api.API; +import org.bdj.api.KernelAPI; +import org.bdj.api.NativeInvoke; + +/** + * Cinebreak Shell - remote command shell for the BD-JB5 chain. + * Runs inside the jailbroken console after the sandbox escape. + * Listen on :9026, speak simple line commands. + */ +public class CinebreakShell { + + private static final int PORT = 9026; + private static final String BANNER = + "Cinebreak Shell 1.0 - kernel R/W over TCP\n" + + "commands: help | notify | kread | kwrite \n" + + " kdump | kstr | ping | quit"; + + public static void main(String[] args) throws Exception { + Status.println("cinebreak-shell starting on :" + PORT); + NativeInvoke.sendNotificationRequest("Cinebreak Shell up on :" + PORT); + + API api = API.getInstance(); + KernelAPI kapi = KernelAPI.getInstance(); + + ServerSocket srv = new ServerSocket(PORT); + Status.println("cinebreak-shell listening"); + + while (true) { + Socket sock = srv.accept(); + try { + BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream())); + PrintWriter out = new PrintWriter(sock.getOutputStream(), true); + out.println(BANNER); + String line; + while ((line = in.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) continue; + String[] t = line.split("\\s+"); + try { + if (t[0].equals("quit")) break; + else if (t[0].equals("help")) out.println(BANNER); + else if (t[0].equals("ping")) out.println("pong"); + else if (t[0].equals("notify")) { + String msg = line.substring("notify".length()).trim(); + out.println("notify ret=" + NativeInvoke.sendNotificationRequest(msg)); + } else if (t[0].equals("kread")) { + long a = Long.decode(t[1]).longValue(); + out.println("0x" + Long.toHexString(kapi.kread32(a) & 0xFFFFFFFFL)); + } else if (t[0].equals("kwrite")) { + long a = Long.decode(t[1]).longValue(); + int v = (int) Long.decode(t[2]).longValue(); + kapi.kwrite32(a, v); + out.println("ok"); + } else if (t[0].equals("kdump")) { + long a = Long.decode(t[1]).longValue(); + int n = Integer.parseInt(t[2]); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n && i < 512; i++) { + int b = kapi.kread8(a + i) & 0xFF; + sb.append("0123456789abcdef".charAt((b >> 4) & 0xF)); + sb.append("0123456789abcdef".charAt(b & 0xF)); + sb.append(' '); + if (i % 16 == 15) sb.append('\n'); + } + out.println(sb.toString()); + } else if (t[0].equals("kstr")) { + long a = Long.decode(t[1]).longValue(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 256; i++) { + int b = kapi.kread8(a + i) & 0xFF; + if (b == 0) break; + sb.append((char) b); + } + out.println(sb.toString()); + } else out.println("unknown: " + t[0]); + } catch (Exception e) { + out.println("err: " + e); + } + } + } finally { + sock.close(); + } + } + } +} diff --git a/tools/kernel-offsets.py b/tools/kernel-offsets.py new file mode 100755 index 0000000..3c7cbc3 --- /dev/null +++ b/tools/kernel-offsets.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +""" +Cinebreak kernel-offsets - locate the 5 offsets Poopsploit needs in a PS5 +kernel dump, so new firmwares don't break the chain. + +Targets (from PS4_KernelOffset.java): + prison0 - first struct prison (root jail) [xref: allproc chain walk] + rootvnode - vnode pointer of the root filesystem + sysent661 - syscall 661 entry in the sysent table + jmpRsi - "jmp rsi" gadget in kernel text + klLock - kernel lock structure + +Usage: kernel-offsets.py scan [--base 0xffffffff80000000] + kernel-offsets.py selftest +""" +import sys, struct + +def scan_gadget(data, pattern, base): + """Find all occurrences of a byte pattern; return (offset, vaddr) pairs.""" + hits, i = [], 0 + while True: + i = data.find(pattern, i) + if i < 0: break + hits.append((i, base + i)) + i += 1 + return hits + +def scan_sysent(data, base, n=661, entry_size=0x10): + """Locate the sysent table by scanning for a plausible entry 661. + Heuristic: 661*entry_size into a table where consecutive entries point + into kernel text with monotonically increasing vaddrs.""" + text_start, text_end = base + 0x1000, base + len(data) + stride = entry_size * n + # candidate table starts where entry[n] points into text AND + # entries n-2..n+2 are also text pointers (dense syscall table) + for i in range(0, len(data) - stride, 0x1000): # 4k-aligned tables + ok = True + for k in range(n - 2, n + 3): + ptr = struct.unpack_from(" "prison_0\0" at 0x600000 + struct.pack_into(" {v}") + +if __name__ == "__main__": + if len(sys.argv) > 1 and sys.argv[1] == "selftest": + selftest() + elif len(sys.argv) >= 3 and sys.argv[1] == "scan": + data = open(sys.argv[2], "rb").read() + base = int(sys.argv[4], 0) if len(sys.argv) > 4 and sys.argv[3] == "--base" else 0xFFFF000000000000 + for k, v in scan(data, base).items(): print(f"{k:12s} -> {v}") + else: + print(__doc__)