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)
This commit is contained in:
benjamin committed 2026-08-19 22:33:42 +02:00
1 parent 4399a8288d
commit 72f4661bdc
72 files changed
+77927 -17

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
# 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.