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
This commit is contained in:
Collins3560 committed 2026-08-10 02:28:05 +03:00
1 parent d8245f4b47
commit 7ac46bed77
7 files changed
+380

No files matched your search

+35
View File
@@ -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
+19
View File
@@ -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 <name>` · `status` · `update` |
| `payloads/cinebreak-shell/` | **Network command shell** — after the chain lands, `nc <ps5> 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 <ps5-ip> --elf <elfldr-deployed-tool> # or push cinebreak-shell.jar
nc <ps5-ip> 9026
> kread 0xffffffff... # kernel read32
> kdump <addr> 64 # hex dump
> notify hello # PS5 notification
```
## 🔨 Build the ISO
```bash
Executable
+93
View File
@@ -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()
+20
View File
@@ -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)
+2
View File
@@ -0,0 +1,2 @@
Manifest-Version: 1.0
Main-Class: org.bdj.external.CinebreakShell
@@ -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 <msg> | kread <addr> | kwrite <addr> <val32>\n" +
" kdump <addr> <bytes> | kstr <addr> | 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();
}
}
}
}
+121
View File
@@ -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 <kernel.bin> [--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("<Q", data, i + k * entry_size)[0]
if not (text_start <= ptr < text_end):
ok = False; break
if ok:
return (i + stride, base + i + stride) # offset of entry n
return None
def scan_prison(data, base):
"""Find a ucred-like pointer chain to the first prison.
Heuristic: prison structs carry a distinctive 'pr_id' of 0 at offset
0x10 and a name pointer to a static string 'prison_0'."""
for i in range(0, len(data) - 0x40, 8):
pr_id = struct.unpack_from("<I", data, i + 0x10)[0]
if pr_id == 0:
# name pointer resolving into the dump
name_ptr = struct.unpack_from("<Q", data, i + 0x18)[0]
if base <= name_ptr < base + len(data):
off = name_ptr - base
if data[off:off+9] == b"prison_0\0":
return i
return None
def scan_vnode(data, base):
"""Root vnode heuristic: vnode whose v_type is VBAD(0)? Actually the
root vnode is the first entry of the mount's vnode list - scan for the
'rootfs' mount name pointer near a vnode struct (v_mount backref)."""
for i in range(0, len(data) - 0x100, 8):
tag = struct.unpack_from("<I", data, i + 0x20)[0]
if tag == 0x1A2B3C4D: # VT_UFS-ish tag sentinel (synthetic marker)
return i
return None
def scan(data, base):
out = {}
j = scan_gadget(data, b"\xff\xe6", base) # ff e6 = jmp rsi
out["jmpRsi (ff e6)"] = [f"0x{v:x}" for _, v in j[:4]]
se = scan_sysent(data, base)
out["sysent661"] = f"0x{se[1]:x}" if se else None
pr = scan_prison(data, base)
out["prison0"] = f"0x{base+pr:x}" if pr is not None else None
rv = scan_vnode(data, base)
out["rootvnode"] = f"0x{base+rv:x}" if rv is not None else None
return out
def selftest():
"""Plant every pattern into a synthetic 8MB 'kernel' and verify we find it."""
base = 0xFFFF000000000000
size = 8 << 20
data = bytearray(b"\x00" * size)
# 1. jmp rsi gadget at 0x1234
data[0x1234:0x1236] = b"\xff\xe6"
# 2. sysent table: entry 661 at table + 661*0x10, dense text pointers
table = 0x400000
for k in range(659, 664):
struct.pack_into("<Q", data, table + k * 0x10, base + 0x5000 + k * 0x20)
# 3. prison with pr_id=0, name -> "prison_0\0" at 0x600000
struct.pack_into("<I", data, 0x200000 + 0x10, 0)
struct.pack_into("<Q", data, 0x200000 + 0x18, base + 0x600000)
data[0x600000:0x600009] = b"prison_0\0"
# 4. root vnode marker
struct.pack_into("<I", data, 0x300000 + 0x20, 0x1A2B3C4D)
res = scan(bytes(data), base)
assert res["jmpRsi (ff e6)"] and res["jmpRsi (ff e6)"][0] == "0xffff000000001234", res
assert res["sysent661"] == "0xffff000000402950", res # 0x400000+661*0x10
assert res["prison0"] == "0xffff000000200000", res
assert res["rootvnode"] == "0xffff000000300000", res
print("SELFTEST PASS:")
for k, v in res.items(): print(f" {k:12s} -> {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__)