Files
plex-docker/Freeloader/README.md
T
benjamin 72f4661bdc Replace patchelf crack with Freeloader LD_PRELOAD approach
- 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)
2026-08-19 22:33:42 +02:00

146 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.