149 lines
7.2 KiB
Markdown
149 lines
7.2 KiB
Markdown
# 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`.
|