- Multi-stage Dockerfile: discover patterns from PMS binary (capstone), compile .so with zig (musl), layer onto lscr.io/linuxserver/plex - Uses LD_PRELOAD instead of patchelf (which corrupts Plex's musl loader) - Auto-discovery: broad structural patterns with string-anchored fallback (//feature) and relationship-based fallback (BITSET_REF within BS_INIT) - hook.cpp uses __has_include for generated patterns with hardcoded fallbacks - Custom wrapper.sh (no traffic_logger preload) - Vendored Freeloader source (github.com/authrequest/Freeloader, AGPL-3.0) - Removed stale plexmediaserver_crack.so binary - Supports Plex 1.43.3+ (verified against 1.43.2 and 1.43.3)
146 lines
7.8 KiB
Markdown
146 lines
7.8 KiB
Markdown
# Plex_Patch
|
||
|
||
Reverse-engineering notes and tooling for **Plex Media Server** on **Linux
|
||
x86-64** — covering both *feature unlocking* and *remote access*.
|
||
|
||
> ⚠️ **Disclaimer** — For educational and reverse-engineering purposes, on
|
||
> software you legally run yourself. Nothing here bypasses account or server
|
||
> authentication, and **no Plex code** is included or redistributed. If you rely
|
||
> on Plex, buy a Plex Pass — it funds the developers. Use at your own risk; no
|
||
> warranty.
|
||
|
||
## What's here
|
||
|
||
| # | Component | Path | Summary |
|
||
|---|-----------|------|---------|
|
||
| 1 | **Feature-unlock patch** | `src/`, `build.sh` | `LD_PRELOAD` shared library: forces every `FeatureManager` bit on **and** adds webhook CRUD via socket interception |
|
||
| 2 | **Relay RE + model** | `plex_relay/` | Reverse-engineered, runnable reimplementation of Plex's `RelayController` |
|
||
| 3 | **Remote access (no patch)** | `scripts/plex-tailnet/` | Reach your server over Tailscale/Headscale instead of Plex Relay |
|
||
| 4 | **Docker support** | `docker/`, [`docs/DOCKER.md`](docs/DOCKER.md) | Patched `plexinc/pms-docker` / `lscr.io/linuxserver/plex` images (multi-stage build) **and** in-place patcher for a running container (`plex-docker-patch.sh`) |
|
||
|
||
Each subsystem has its own README; this page is the map.
|
||
|
||
---
|
||
|
||
## 1 · Feature-unlock patch
|
||
|
||
Plex's feature gates read a single in-memory table, `g_feature_bitset_slots`
|
||
(14 × `uint64`), populated from the MyPlex feature list. A feature with internal
|
||
code `C` is "available" iff `slots[C >> 3] & (1 << (C & 7))`. The patch (`src/`)
|
||
is a small shared library whose constructor finds
|
||
`FeatureManager_apply_feature_list_xml`, installs a trampoline, and forces all 14
|
||
slots to `0xFF…FF` after Plex applies its feature list — so every feature
|
||
(including Plex Pass, code 92) reads as enabled.
|
||
|
||
Two non-obvious requirements make or break this on a real install:
|
||
|
||
1. **Build against musl, not glibc.** Plex bundles its own musl libc + libgcompat
|
||
(`/usr/lib/plexmediaserver/lib/`). A glibc-built `.so` fails to relocate
|
||
glibc-only symbols and Plex exits 127. The build uses `zig` to target
|
||
`x86_64-linux-musl`.
|
||
2. **Inject with `LD_PRELOAD`, never `patchelf`.** `patchelf --add-needed`
|
||
corrupts the PIE under musl's loader (instant SIGSEGV). A tiny launcher sets
|
||
`LD_PRELOAD` only for the Plex `exec`, and the library `unsetenv`s it so
|
||
Plex's glibc helper children are unaffected.
|
||
|
||
```bash
|
||
bash build.sh # -> build/plexmediaserver_crack.so (musl); prints install steps
|
||
```
|
||
|
||
**Webhook socket interceptor.** The same `.so` also hooks POSIX socket functions
|
||
(`read`, `recvfrom`, `sendmsg`) to intercept `/api/v2/user/webhooks` HTTP requests
|
||
made by the Plex Web client to the local server. Instead of returning PMS's native
|
||
404 (the endpoint only exists on plex.tv, not locally), the hook serves a complete
|
||
webhook CRUD API backed by a JSON file at `/var/lib/plexmediaserver/webhooks.json`:
|
||
|
||
- `GET /api/v2/user/webhooks` — list all webhooks
|
||
- `POST /api/v2/user/webhooks` — add webhook(s) from `urls[]=` form body
|
||
- `PUT /api/v2/user/webhooks/:id` — update a webhook
|
||
- `DELETE /api/v2/user/webhooks/:id` — delete a webhook
|
||
- `OPTIONS` — CORS preflight
|
||
|
||
After every mutating operation, the hook calls into Plex's in-process
|
||
`WebhookManager` to refresh the dispatch vector, so changes take effect without
|
||
a server restart. The webhook file path can be overridden with the
|
||
`PLEX_WEBHOOKS_FILE` environment variable. The Plex Web bundle also needs a
|
||
one-time static patch so its JavaScript talks to `window.location.origin`
|
||
instead of the Plex cloud API — see [`AGENTS.md`](AGENTS.md) for details.
|
||
|
||
Full build / install / uninstall guide: **[`docs/BUILD.md`](docs/BUILD.md)**.
|
||
|
||
## 2 · Plex Relay — `plex_relay/`
|
||
|
||
A study of how Plex makes a server reachable when no direct connection exists: it
|
||
opens a **reverse SSH tunnel to a Plex-operated relay host**. `plex_relay/` is a
|
||
clean-room, dependency-free Python reimplementation of the `RelayController`
|
||
translation unit (key fetch + 24h cache, `relayHostKey.txt` pinning, the ssh
|
||
tunnel, the 300s reaper), with a typed error model, injected I/O seams, and a
|
||
full test suite. See **[`plex_relay/README.md`](plex_relay/README.md)**.
|
||
|
||
## 3 · Remote access without patching — `scripts/plex-tailnet/`
|
||
|
||
The pragmatic alternative to both Plex Relay and patching: put the server and its
|
||
viewers on a **Tailscale/Headscale mesh VPN** and let Plex publish the tailnet
|
||
address. Includes an idempotent setup script (security questionnaire, firewall
|
||
lockdown, health check), an optional self-hosted Headscale installer, and a
|
||
shared shell library. See **[`scripts/plex-tailnet/README.md`](scripts/plex-tailnet/README.md)**.
|
||
|
||
## 4 · Docker support — `docker/`
|
||
|
||
Same `LD_PRELOAD`-on-the-PMS-exec patch, packaged for the two popular Plex
|
||
container images. Two flows are supported:
|
||
|
||
- **Rebuild a patched image** — multi-stage Dockerfiles (`Dockerfile.plexinc`,
|
||
`Dockerfile.linuxserver`) build the musl `.so` with `zig`, layer it onto
|
||
the upstream image, and replace the s6 `svc-plex` `run` file. Best for
|
||
repeat deploys and CI/CD.
|
||
- **Patch a running container in place** — `plex-docker-patch.sh` modifies
|
||
the live container's filesystem (`.so`, wrapper, s6 `run` file) and
|
||
restarts it. No image rebuild, original image untouched, fully
|
||
revertible. Best for one-off patching of a container you don't want
|
||
to touch.
|
||
|
||
The wrapper sets `LD_PRELOAD` *last* and the `.so`'s constructor `unsetenv`s
|
||
it, so glibc helper children (Tuner, Script Host, transcoders) are unaffected.
|
||
See **[`docker/README.md`](docker/README.md)** and the full guide
|
||
**[`docs/DOCKER.md`](docs/DOCKER.md)**.
|
||
|
||
---
|
||
|
||
## Repository layout
|
||
|
||
| Path | What |
|
||
|------|------|
|
||
| `src/hook.cpp` · `hook.hpp` | hooking engine: `dl_iterate_phdr` discovery, signature scan, trampoline, feature logic, feature-UUID catalog |
|
||
| `src/main.cpp` | library constructor (`unsetenv` + `hook()`) |
|
||
| `src/webhook_handler.cpp` · `webhook_handler.hpp` | socket-level HTTP interceptor: hooks `read`/`recvfrom`/`sendmsg` to intercept `/api/v2/user/webhooks` and serve local CRUD from a JSON file |
|
||
| `build.sh` | musl build via `zig` (auto-downloaded) with an ABI sanity gate |
|
||
| `scripts/plex-crack-wrapper.sh` | systemd `ExecStart` launcher scoping `LD_PRELOAD` to the Plex process |
|
||
| `scripts/readbitset.py` | verifier: dumps the live feature bitset from a running PMS |
|
||
| `scripts/plex-tailnet/` | Tailscale/Headscale remote-access setup (see its README) |
|
||
| `plex_relay/` | Python reimplementation of Plex's `RelayController` (see its README) |
|
||
| `windows/` | Windows x64 DLL injector + godmode patch (see its README) |
|
||
| `docker/` | patched `plexinc/pms-docker` + `lscr.io/linuxserver/plex` images + in-place patcher for running containers (see its README) |
|
||
| `third_party/zydis/` | vendored [Zydis](https://github.com/zyantific/zydis) disassembler (MIT) |
|
||
| `docs/BUILD.md` | native Linux build / install / uninstall guide |
|
||
| `docs/DOCKER.md` | Docker build / run / verify / uninstall / troubleshooting guide |
|
||
| `docs/WINDOWS.md` | top-level Windows x64 patching/build index |
|
||
| `experimental/debug_hook.c` | standalone alternate hook (legacy signature) |
|
||
| `AGENTS.md` | architecture / RE notes |
|
||
|
||
## Not in this repo (by design)
|
||
|
||
The copyrighted Plex binaries (`Plex Media Server`, `libsoci_core.so`), the IDA
|
||
Pro databases (`*.i64`, `*.id0`, …), the auto-downloaded `toolchain/`, and any
|
||
local machine config (`.mcp.json`, keys, `.env`) are intentionally
|
||
**git-ignored** — they are large, sensitive, or not ours to distribute. Point
|
||
your own analysis tools at your own Plex install.
|
||
|
||
## License
|
||
|
||
[GNU AGPL-3.0-or-later](LICENSE) © the Plex_Patch authors. Each source file
|
||
carries an `SPDX-License-Identifier: AGPL-3.0-or-later` tag.
|
||
|
||
The vendored Zydis disassembler in `third_party/zydis/` is **MIT**-licensed (see
|
||
`third_party/zydis/README.md`); its terms are preserved and unaffected.
|