# Phase 0.9E-R official Y2JB port audit Date: 2026-07-18 Port-9020 classification: `PORT_9020_REFERENCE_ONLY` ## Exact 9020 search Every official tag (`Y2JB-1.2.1`, `1.3`, `1.4`, `1.5`, `1.6`) has exactly two 9020 text references: - `README.md`: a `payload_sender.py ... 9020 payload.bin` example; - `payload_sender.py:29`: the same usage example. No tag contains a socket bind, listen, connect target, parser, mapper, ELF loader, or dispatch function fixed to port 9020. Therefore the example cannot be promoted to an implementation. ## Actual Remote JS Loader At release 1.6 commit `0dbbf4e7e0203af7e5d101a3256c634edf4e3ba2`, the implementation is `download0/cache/splash_screen/aHR0cHM6Ly93d3cueW91dHViZS5jb20vdHY=/remotejsloader.js`. | Lines | Function / block | Contract | |---|---|---| | 2–8 | allocation | fixed `MAXSIZE = 500 * 1024`; one 512000-byte receive buffer | | 10–39 | `create_socket` | TCP/IPv4, `SO_REUSEADDR`, `INADDR_ANY`, port 0, backlog 3 | | 41–47 | `get_port` | reads assigned ephemeral port | | 49–88 | `setup_socket_until_port_50000` | creates/closes up to 60000 sockets seeking assigned port 50000; falls back to last port | | 90–105 | `recreate_socket` | creates a new ephemeral listener after accept failure | | 107–119 | initial setup | reports actual IP and port | | 122–209 | accept loop | infinite accept/read/decode/eval loop | | 145–164 | receive loop | reads until EOF or 512000 bytes; partial reads accumulate | | 168–188 | validation | rejects empty/error; 1.6 rejects ELF magic only | | 190–203 | dispatch | UTF-8 decode, `eval`, same-connection log writes, close in `finally` | Protocol properties: - server: PS5-side JavaScript listener; - expected port: normally 50000, but explicitly dynamic; - framing: TCP connection close/EOF; - maximum accepted prefix: 512000 bytes; - content: JavaScript; 1.6 rejects only `7f 45 4c 46`; - authentication: none; - declared length, version, checksum, or hash: none; - timeout: none; - full-limit rejection: absent; reaching 512000 bytes can dispatch the prefix without proving EOF; - native mapping/entrypoint: absent; received bytes are text-decoded and evaluated; - filesystem write: none in this receive function; - retry behavior: internal listener allocation/recreation loops exist; - crash/hang risk: untrusted JavaScript evaluation, unbounded accept lifetime, blocking reads, and no deadline. The 1.2.1/1.3 loader uses Git blob `21663be4a0fab6b6a829824946f54706fb1be839`; 1.4/1.5 use `decb42ba0bbd2bdc8d1568d33b6d772f9fe24d8e`; 1.6 uses `dd4957530e7a88c889877a7dcb3433642a71e019`. Release 1.6 added ELF-magic rejection. Release 1.4 added streaming logs over the accepted connection. This is a concrete official implementation, but it is not a port-9020 implementation. ## Lapse and port 9021 The official chain is: `Remote JS Loader (~50000) → lapse.js → load_aioshellcode → embedded kexp + elfldr bytes → elfldr listener 9021` Relevant source: - `payloads/lapse.js:1707-1717` requires `load_aioshellcode` and rejects firmware above 10.01; - `payloads/lapse.js:1725-1734` creates a per-run `lapse.fail` marker; - `payloads/lapse.js:1639` calls `load_aioshellcode` after exploit setup; - `aioshellcode.js:12-24` locates embedded files in sandbox slots; - `aioshellcode.js:26-45` maps the kexp binary with JIT shared executable memory; - `aioshellcode.js:47-76` starts and joins its thread; - `aioshellcode.js:78-90` reads the embedded elfldr into process memory; - `aioshellcode.js:107-117` passes pipe state, `allproc`, elfldr address, and size to the embedded binary; - `misc.js:262-302` treats a successful loopback connection to 9021 as a jailbroken/elfldr-running signal; - `README.md:127-137` tells the host to send an ELF to 9021 after Lapse. The port-9021 listener itself is contained in an ELF binary. Y2JB contains no generator or listener source for the 1.6 ELF. Release 1.5 embeds the already pinned public elfldr v0.23 asset byte-for-byte; release 1.6 embeds a distinct binary. No 1.6 elfldr source commit is invented. ## Safety classification The Remote JS Loader is independent of an already running elfldr and Payload Manager at source-architecture level, but deployed use is unbound because the local backup is not an official outer asset. It also uses ~50000, not 9020. Consequently: - `PORT_9020_IMPLEMENTATION_FOUND`: false; - `PORT_9020_IMPLEMENTATION_PARTIAL`: false; - `PORT_9020_REFERENCE_ONLY`: true; - Phase 0.9F offline design allowed: false. ## Security and robustness findings | Finding | Classification | Evidence and consequence | |---|---|---| | Receiver dispatches UTF-8 through `eval` | `SESSION_CRASH_RISK` | arbitrary JavaScript can hang or terminate the current exploit session | | No receiver deadline; infinite accept loop | `SESSION_CRASH_RISK` | blocked reads and repeated listener lifetime have no bounded cleanup | | Buffer limit can dispatch without proven EOF | `PROTOCOL_INTEGRITY_RISK` | a 512000-byte prefix can be treated as complete | | No length, authentication, checksum, or hash | `PROTOCOL_INTEGRITY_RISK` | source does not bind received bytes to an intended request | | Sender has no timeout, acknowledgement, response read, or hash | `PROTOCOL_INTEGRITY_RISK` | local `sendall` completion is not peer receipt or execution proof | | Lapse creates `common_temp/lapse.fail` | `UNKNOWN` | source proves a filesystem side effect, but deployed persistence and reboot semantics remain unobserved | | Lapse/kexp intentionally changes kernel state | `BRICK_RELEVANT` | it is outside every authorization in this phase and cannot be treated as read-only | | Embedded 1.6 elfldr has no source/generator | `UNKNOWN` | listener bounds, parser, mapping, cleanup, and crash behavior cannot be source-audited here | | Sender usage errors return implicit success | `HOST_ONLY` | automation could misread a local invocation error; no device action occurred | A session crash is not automatically a brick: the reviewed Remote JS receive function does not itself replace a live file or enable autoload. However, the local backup is unbound and the subsequent Lapse path contains explicit kernel and filesystem effects, so no device-risk conclusion or recovery guarantee is promoted from that source architecture. ## Firmware selection and reboot boundary At release 1.6: - `main.js:681-729` identifies supported YouTube app layouts and labels versions `01.000.003`, `01.000.030`, and `01.009.202` with minimum firmware 4.03, 12.20, and 13.20 respectively; - `misc.js:92-108` obtains `kern.sdk_version` and formats the firmware value; - `main.js:967-971` assigns and reports that value; - `payloads/lapse.js:1707-1717` requires `load_aioshellcode` and rejects versions above 10.01. Thus 9.60 passes the release-source comparison in Lapse. It does not prove that release 1.6 is deployed, that its masks match the device, or that the runtime chain succeeds. Listener sockets, JavaScript state, mappings, threads, and loaded code are process- or kernel-resident in source architecture. The Y2JB cache and `lapse.fail` path are filesystem state. Source alone does not prove which state a reboot removes, whether the original trigger remains usable, or whether a crash permits clean restart. The classification stays `REBOOT_RECOVERY_UNPROVEN`.