# 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.