#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Generate the exact Phase-0.6 PS5 loader/runtime audit without execution.""" from __future__ import annotations import argparse import hashlib import json import re import subprocess from pathlib import Path from typing import Any ELFLDR_COMMIT = "699e8bcff03e91e8d6ca6eba281af25c5a58d8c2" ELFLDR_SHA256 = "092d16ee0ede0c494947efd38d1a17bbd7cc4b022d3858ea898833c188c703e8" ELFLDR_SIZE = 397000 PLDMGR_COMMIT = "cfbc70f30f419b09bf2b52283f7409e2d3117ee1" PLDMGR_SHA256 = "518740adbacccb9094fadb07dd424c53ee290f38306449ccc9d6957fdf813c0b" SDK_COMMIT = "d2e2e585740362976a39fdd5ccf390f199a7bc37" def digest_bytes(data: bytes) -> str: return hashlib.sha256(data).hexdigest() def digest_file(path: Path) -> str: return digest_bytes(path.read_bytes()) def run(command: list[str]) -> str: result = subprocess.run( command, check=True, capture_output=True, text=True, encoding="utf-8", errors="replace", ) return result.stdout.replace("\r\n", "\n") def git_identity(path: Path, expected: str) -> dict[str, Any]: git = ["git", "-c", "core.autocrlf=true", "-C", str(path)] commit = run([*git, "rev-parse", "HEAD"]).strip() if commit != expected: raise ValueError(f"{path}: expected commit {expected}, got {commit}") status = run([*git, "status", "--porcelain"]) if status: raise ValueError(f"{path}: source checkout is dirty") return {"commit": commit, "dirty": False} def source_record(root: Path, path: Path) -> dict[str, Any]: data = path.read_bytes().replace(b"\r\n", b"\n") return { "normalization": "lf", "path": path.relative_to(root).as_posix(), "sha256": digest_bytes(data), "size": len(data), } def require_tokens(path: Path, tokens: list[str]) -> None: text = path.read_text(encoding="utf-8") missing = [token for token in tokens if token not in text] if missing: raise ValueError(f"{path}: required evidence missing: {missing}") def parse_binary(readelf_output: str, disassembly: str) -> dict[str, Any]: entry_match = re.search(r"Entry point address:\s+(0x[0-9a-f]+)", readelf_output) relocation_match = re.search( r"Relocation section '\.rela\.dyn'.*contains (\d+) entries", readelf_output, ) relative_match = re.search(r"\(RELACOUNT\)\s+(\d+)", readelf_output) if entry_match is None or relocation_match is None or relative_match is None: raise ValueError("readelf output lacks required header/relocation evidence") needed = sorted(re.findall(r"\(NEEDED\).*\[([^\]]+)\]", readelf_output)) dynsym_match = re.search( r"Symbol table '\.dynsym'.*?\n(?P.*?)(?:\nSymbol table|\Z)", readelf_output, flags=re.DOTALL, ) if dynsym_match is None: raise ValueError("readelf output lacks .dynsym") undefined: list[str] = [] for line in dynsym_match.group("body").splitlines(): match = re.search(r"\bUND\s+(\S+)\s*$", line) if match and match.group(1): undefined.append(match.group(1)) array_sizes: dict[str, int] = {} for name in ("PREINIT_ARRAY", "INIT_ARRAY", "FINI_ARRAY"): match = re.search(rf"\({name}SZ\)\s+(\d+)", readelf_output) if match is None: raise ValueError(f"readelf output lacks {name}SZ") array_sizes[name.lower()] = int(match.group(1)) load_segments = [] for line in readelf_output.splitlines(): match = re.match( r"\s*LOAD\s+\S+\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+([RWE ]+)\s+\S+", line, ) if match: load_segments.append( { "file_size": int(match.group(1), 16), "memory_size": int(match.group(2), 16), "permissions": match.group(3).replace(" ", ""), } ) return { "disassembly_sha256": digest_bytes(disassembly.encode("utf-8")), "dt_needed": needed, "entry_point": entry_match.group(1), "init_fini_array_sizes": array_sizes, "load_segments": load_segments, "readelf_report_sha256": digest_bytes(readelf_output.encode("utf-8")), "relocations": { "relative": int(relative_match.group(1)), "total": int(relocation_match.group(1)), }, "tls_present": bool( re.search(r"^\s*TLS\s", readelf_output, flags=re.MULTILINE) or re.search(r"\.(?:tdata|tbss)\b", readelf_output) ), "undefined_dynamic_symbols": sorted(undefined), } def effect( effect_id: str, classification: str, scope: str, evidence: list[str], blocker: bool, ) -> dict[str, Any]: return { "blocker": blocker, "classification": classification, "evidence": evidence, "id": effect_id, "scope": scope, } def build_audit(args: argparse.Namespace) -> dict[str, Any]: root = args.root.resolve() loader_source = args.loader_source.resolve() manager_source = args.payload_manager_source.resolve() sdk_source = args.sdk_source.resolve() asset = args.loader_asset.resolve() loader_git = git_identity(loader_source, ELFLDR_COMMIT) manager_git = git_identity(manager_source, PLDMGR_COMMIT) sdk_git = git_identity(sdk_source, SDK_COMMIT) if asset.stat().st_size != ELFLDR_SIZE or digest_file(asset) != ELFLDR_SHA256: raise ValueError("elfldr release asset identity mismatch") loader_files = [ loader_source / name for name in ("main.c", "elfldr.c", "elfldr.h", "pt.c", "pt.h", "socksrv.c", "Makefile") ] manager_files = [ manager_source / name for name in ( "src/ps5_launcher.c", "src/http_server.c", "src/autoload.c", "src/main.c", "src/payload_mgr.c", "include/pldmgr.h", "Makefile", ) ] sdk_files = [ sdk_source / name for name in ( "crt/crt.c", "crt/patch.c", "crt/kernel.c", "include/ps5/payload.h", "crt/Makefile", ) ] require_tokens( loader_source / "elfldr.c", [ "rfork_thread(RFPROC | RFCFDG | RFMEM", "execve(SceSpZeroConf, argv, 0)", "pt_setlong(pid, r.r_rsp-8, r.r_rip)", "r.r_rip = entry", "r.r_rdi = args", "kernel_overlap_sockets", "kernel_set_ucred_uid(pid, 0)", "pt_detach(pid, 0)", ], ) require_tokens( loader_source / "pt.c", [ "kernel_set_ucred_authid(mypid, 0x4800000000010003l)", "kernel_set_ucred_authid(mypid, authid)", "while(jmp_reg.r_rsp <= bak_reg.r_rsp)", ], ) require_tokens( loader_source / "main.c", ["kernel_set_qaflags(qa_flags)", "elfldr_raise_privileges(mypid)"], ) require_tokens( loader_source / "socksrv.c", ["signal(SIGCHLD, SIG_IGN)", "while(1)", "serve_elfldr(port)"], ) require_tokens( manager_source / "src/ps5_launcher.c", ['server_addr.sin_addr.s_addr = inet_addr("127.0.0.1")', "send(sock"], ) require_tokens( manager_source / "src/http_server.c", [ "ps5_launch_elf(final_path)", "fopen(path, \"wb\")", "payload_mgr_import_to_storage", ], ) require_tokens( sdk_source / "crt/crt.c", ["__patch_init()", "payload_terminate(void)", "_start(payload_args_t *args)"], ) readelf_output = run( [ str(args.readelf), "-h", "-l", "-S", "-d", "-r", "-s", "-W", str(asset), ] ) disassembly = run( [str(args.objdump), "-d", "--no-show-raw-insn", str(asset)] ) binary = parse_binary(readelf_output, disassembly) effects = [ effect( "elfldr_first_stage_qaflags_enable", "EXPECTED_VOLATILE_RUNTIME_EFFECT", "existing_loader_bootstrap", ["elfldr/main.c:48-59"], False, ), effect( "elfldr_first_stage_privilege_restore", "UNBOUNDED_OR_UNKNOWN", "existing_exploit_host_process", [ "elfldr/main.c:61-105 restores jail/root/caps/authid", "UID is changed by elfldr_raise_privileges but is not backed up or restored", ], True, ), effect( "payload_process_creation", "PAYLOAD_PROCESS_LOCAL", "new_SceSpZeroConf_child", ["elfldr/elfldr.c:570-710"], False, ), effect( "ptrace_authid_restore_success_path", "RESTORED_BY_LOADER", "elfldr_service_process", ["elfldr/pt.c:35-54"], False, ), effect( "ptrace_authid_restore_failure_path", "UNBOUNDED_OR_UNKNOWN", "elfldr_service_process", [ "elfldr/pt.c:50-51 returns after failed restoration", "no second restoration or process shutdown is present", ], True, ), effect( "ptrace_single_step_completion", "UNBOUNDED_OR_UNKNOWN", "loader_control_path", ["elfldr/pt.c:238-246", "elfldr/pt.c:291-299"], True, ), effect( "breakpoint_byte", "RESTORED_BY_LOADER", "payload_child", ["elfldr/elfldr.c:675-700"], False, ), effect( "breakpoint_page_permissions", "PAYLOAD_PROCESS_LOCAL", "payload_child", [ "elfldr/elfldr.c:669 changes page to RWX", "no source edge restores the original protection", ], False, ), effect( "payload_credentials", "UNBOUNDED_OR_UNKNOWN", "payload_child", [ "elfldr/elfldr.c:447-513 restores jail/root/caps/authid", "UID is set to zero and is not restored", ], True, ), effect( "payload_mapping_args_sockets_pipes", "UNBOUNDED_OR_UNKNOWN", "payload_child", [ "elfldr/elfldr.c:143-343", "successful detach has no explicit unmap/close cleanup", "cleanup depends on an unproven child termination path", ], True, ), effect( "sdk_patch_init", "PAYLOAD_PROCESS_LOCAL", "payload_child", ["sdk/crt/crt.c", "sdk/crt/patch.c"], False, ), effect( "sdk_termination_branch", "UNBOUNDED_OR_UNKNOWN", "payload_child", [ "sdk/crt/crt.c payload_terminate may return, call exit, or trap", "the exact branch for the injected SceSpZeroConf child is not proven", ], True, ), effect( "payload_runtime_limit", "UNBOUNDED_OR_UNKNOWN", "detached_payload_child", [ "elfldr/elfldr.c:703-710 detaches and returns the PID", "no 2000 ms watchdog, wait, kill, or retry budget is present", ], True, ), effect( "child_reaping", "UNBOUNDED_OR_UNKNOWN", "elfldr_service_process", [ "elfldr/socksrv.c:400 ignores SIGCHLD", "post-detach exit/resource cleanup semantics are not documented", ], True, ), effect( "payload_manager_launch_hash_binding", "UNBOUNDED_OR_UNKNOWN", "payload_manager_to_elfldr", [ "pldmgr/http_server.c resolves a path then calls ps5_launch_elf", "pldmgr/ps5_launcher.c streams bytes without calculating or checking SHA-256", ], True, ), effect( "payload_manager_upload", "PERSISTENT_WRITE", "payload_manager_storage", [ "pldmgr/http_server.c writes /data/pldmgr/payloads/.tmp", "the upload is committed into payload storage", ], True, ), ] source_files = { "elfldr": [source_record(root, path) for path in loader_files], "payload_manager": [source_record(root, path) for path in manager_files], "sdk": [source_record(root, path) for path in sdk_files], } hard_blockers = [ item["id"] for item in effects if item["classification"] in ("PERSISTENT_WRITE", "UNBOUNDED_OR_UNKNOWN") and item["blocker"] ] hard_blockers.extend( [ "exact_exploit_and_autoloader_identity_unproven", "firmware_9_60_not_independently_device_attested", "return_continuation_after_payload_start_unproven", ] ) return { "artifact": { "built": False, "execution_eligible": False, "filename": None, "sha256": None, "size": None, }, "binary_evidence": binary, "callgraph": { "entry": "Payload Manager /loadpayload:", "edges": [ ["Payload Manager /loadpayload:", "ps5_launch_elf"], ["ps5_launch_elf", "connect 127.0.0.1:9021"], ["ps5_launch_elf", "send ELF bytes"], ["serve_elfldr", "elfldr_spawn"], ["elfldr_spawn", "rfork_thread"], ["rfork_thread child", "elfldr_rfork_entry"], ["elfldr_rfork_entry", "ptrace PT_TRACE_ME"], ["elfldr_rfork_entry", "execve SceSpZeroConf"], ["elfldr_spawn parent", "pt_syscall 599"], ["elfldr_spawn parent", "install then restore INT3 byte"], ["elfldr_spawn parent", "elfldr_exec"], ["elfldr_exec", "elfldr_raise_privileges"], ["elfldr_exec", "elfldr_prepare_exec"], ["elfldr_prepare_exec", "elfldr_load"], ["elfldr_prepare_exec", "elfldr_payload_args"], ["elfldr_prepare_exec", "push observed RIP at RSP-8"], ["elfldr_prepare_exec", "set RIP=payload entry"], ["elfldr_prepare_exec", "set RDI=payload_args"], ["elfldr_exec", "restore subset of credentials"], ["elfldr_exec", "ptrace PT_DETACH"], ["payload _start", "SDK __patch_init"], ["payload _start", "payload main"], ["payload _start", "payload_terminate"], ["payload_terminate", "return or exit or trap"], ], "extraction": "reviewed source edges with required-token assertions", }, "decision": "BLOCKED_VERSION_OR_UNBOUNDED_EFFECT", "effects": effects, "firmware": { "device_attested": False, "exact": "9.60", "evidence": "user_provided_only", }, "hard_blockers": sorted(hard_blockers), "identity": { "elfldr": { **loader_git, "installed_asset_hash_match": True, "observed_inventory_path": ( "/data/pldmgr/payloads/elfldr/elfldr_v0.23.elf" ), "observed_inventory_sha256": ELFLDR_SHA256, "observed_inventory_version": "v0.23", "release": "v0.23", "release_asset_sha256": ELFLDR_SHA256, "release_asset_size": ELFLDR_SIZE, "repository": "https://github.com/ps5-payload-dev/elfldr.git", }, "exact_exploit_autoloader": { "identified": False, "local_candidate": { "filename": "Y2JB-Autoloader-403-1240.zip", "sha256": ( "805e3f87f0c371223619ffc7d3a7b3c0d41a1fae8a8b1171d9e2f162659e8291" ), "size": 504159435, "status": "local_backup_candidate_not_installed_identity_proof", }, "status": "UNPROVEN", }, "payload_manager": { **manager_git, "installed_asset_hash_match": True, "observed_inventory_path": ( "/data/pldmgr/payloads/pldmgr/pldmgr_v0.3.1.elf" ), "observed_inventory_sha256": PLDMGR_SHA256, "observed_inventory_version": "v0.3.1", "observed_version_endpoint": "0.3.1", "release": "v0.3.1", "release_asset_sha256": PLDMGR_SHA256, "repository": "https://github.com/itsPLK/ps5-payload-manager.git", }, "sdk": { **sdk_git, "release": "v0.41", "repository": "https://github.com/ps5-payload-dev/sdk.git", }, }, "no_console_actions": { "elf_executed": False, "elf_transferred": False, "gnm": False, "raw_port_9021_contacted": False, "rendering": False, "videoout": False, }, "observation_scope": { "date": "2026-07-17", "payload_manager_routes": [ "/autoload_status", "/get_config", "/list_payloads", "/log", "/sources_list", "/version", ], "payload_manager_routes_read_only": True, "strict_status_port_744_result": "ECONNREFUSED", }, "phase": "0.6", "schema_version": 1, "source_evidence": source_files, } def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--root", type=Path, required=True) parser.add_argument("--loader-source", type=Path, required=True) parser.add_argument("--loader-asset", type=Path, required=True) parser.add_argument("--payload-manager-source", type=Path, required=True) parser.add_argument("--sdk-source", type=Path, required=True) parser.add_argument("--readelf", type=Path, required=True) parser.add_argument("--objdump", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() try: document = build_audit(args) output = args.output.resolve() output.parent.mkdir(parents=True, exist_ok=True) output.write_text( json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8", newline="\n", ) print( "Phase-0.6 loader audit: BLOCKED_VERSION_OR_UNBOUNDED_EFFECT; " f"{len(document['hard_blockers'])} hard blockers" ) return 0 except (OSError, subprocess.CalledProcessError, ValueError) as error: print(f"Phase-0.6 loader audit failed: {error}") return 1 if __name__ == "__main__": raise SystemExit(main())