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:
1 parent
4399a8288d
commit
72f4661bdc
72 files changed
+77927
-17
No files matched your search
@@ -0,0 +1,159 @@
|
||||
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
||||
# Plex_Patch — Windows x64
|
||||
|
||||
Feature-unlock patch for **Plex Media Server** on **Windows x64**. Port of the
|
||||
Linux `LD_PRELOAD` approach to a DLL injector model.
|
||||
|
||||
> ⚠️ **Disclaimer** — Educational / reverse-engineering only, on software you
|
||||
> legally own and run yourself. No Plex code is shipped. Use at your own risk.
|
||||
|
||||
---
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
plex_inject.exe ──CreateRemoteThread(LoadLibraryA)──▶ Plex Media Server.exe
|
||||
│
|
||||
plex_patch.dll (DllMain)
|
||||
│
|
||||
┌────────────────────────────────┘
|
||||
▼
|
||||
1. Parse PE image (base, .text, .data bounds)
|
||||
2. Version guard: scan for "1.43.2.10687" — refuse if wrong
|
||||
3. Resolve g_feature_bitset via RVA 0x1D9E670 (bounds-checked)
|
||||
4. Immediate force: 14 × InterlockedExchange(0xFFFFFFFF)
|
||||
5. Hook FeatureManager_set_features_from_uuids (Zydis trampoline)
|
||||
→ calls original, then re-forces all bits
|
||||
6. Guard thread: polls 2s, re-forces if any dword reverted
|
||||
```
|
||||
|
||||
Two complementary mechanisms keep every feature bit on:
|
||||
|
||||
- **Hook** (`trampoline.h`): an inline 14-byte `jmp [rip+0]` redirect on the
|
||||
populator function. After the original runs (so Plex's internal state is
|
||||
consistent), the hook atomically forces all 14 dwords to `0xFFFFFFFF`. This
|
||||
is deterministic — every MyPlex refresh immediately becomes "all-enabled."
|
||||
- **Guard thread** (`feature_patch.h`): belt-and-suspenders. Polls at 2 s
|
||||
and re-forces if any dword reverted — catches code paths that write the
|
||||
bitset outside the hooked function, or a failed hook install.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
windows/
|
||||
├── src/
|
||||
│ ├── log.h zero-alloc OutputDebugString logging
|
||||
│ ├── pe_image.h PE base, section bounds, version guard, RVA resolution
|
||||
│ ├── sig_scan.h byte-pattern scanner + RIP-relative resolver
|
||||
│ ├── trampoline.h x64 inline hook engine (14-byte, Zydis prologue decode)
|
||||
│ ├── feature_patch.h orchestration: discover → hook → force → guard
|
||||
│ ├── dllmain.cpp DLL entry (defers work to a thread: loader-lock safe)
|
||||
│ └── injector.cpp attach-to-running or launch-suspended injector
|
||||
├── build.bat zig 0.13.0 build (reuses vendored Zydis)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**Layering (inward dependencies only):**
|
||||
|
||||
| Layer | Module | Depends on |
|
||||
|-------|--------|------------|
|
||||
| Primitives | `log.h` | `<windows.h>` only |
|
||||
| Discovery | `pe_image.h` | `log` |
|
||||
| Discovery | `sig_scan.h` | nothing (pure) |
|
||||
| Engine | `trampoline.h` | `log`, Zydis |
|
||||
| Orchestration | `feature_patch.h` | `pe_image`, `trampoline`, `log` |
|
||||
| Entry | `dllmain.cpp` | `feature_patch` |
|
||||
| Launcher | `injector.cpp` | `<windows.h>` only (standalone binary) |
|
||||
|
||||
### Design decisions
|
||||
|
||||
- **RVA + version guard** over blind signature scan as the primary discovery.
|
||||
The version string must be present in the image before any hardcoded RVA is
|
||||
used, so a wrong build gets a clean refusal, not memory corruption.
|
||||
`sig_scan.h` is included for future update-resilience.
|
||||
- **Hook + guard thread** (belt and suspenders) over either alone. The hook
|
||||
catches refreshes deterministically; the guard catches edge cases and
|
||||
compensates if the hook fails. Either alone would work; together they are
|
||||
robust.
|
||||
- **No Zydis for the injector.** Only the DLL links Zydis (for prologue
|
||||
decode). The injector is a small standalone binary using only kernel32.
|
||||
- **`InterlockedExchange`** for all bitset writes, matching the binary's own
|
||||
atomic store sequence exactly (not a memset; each dword is written atomically).
|
||||
- **DllMain defers to a thread.** Heavy work (PE parsing, hook install, guard
|
||||
spawn) runs outside the loader lock, avoiding the DllMain deadlock trap.
|
||||
|
||||
### Security
|
||||
|
||||
- Version guard: the patch refuses to activate on an unknown build.
|
||||
- Bounds-check: RVAs are validated against the PE section table.
|
||||
- No shell: the injector passes an argv list, never a command string.
|
||||
- The DLL logs to `OutputDebugString`, never to disk (no file creation).
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
Requires `zig` 0.13.0 (same as the Linux build; auto-downloaded to
|
||||
`toolchain/` by `build.sh`).
|
||||
|
||||
```bat
|
||||
cd windows
|
||||
build.bat
|
||||
```
|
||||
|
||||
Outputs:
|
||||
- `build\plex_patch.dll` (723 KB — includes vendored Zydis)
|
||||
- `build\plex_inject.exe` (153 KB)
|
||||
|
||||
## Use
|
||||
|
||||
**Attach to a running PMS:**
|
||||
```bat
|
||||
build\plex_inject.exe
|
||||
```
|
||||
|
||||
**Launch PMS through the injector (recommended — hook installs before features load):**
|
||||
```bat
|
||||
build\plex_inject.exe --launch "C:\Program Files\Plex\Plex Media Server\Plex Media Server.exe"
|
||||
```
|
||||
|
||||
Copy both files to any directory; the injector resolves `plex_patch.dll`
|
||||
relative to its own path.
|
||||
|
||||
**Verify:** open [DebugView](https://learn.microsoft.com/en-us/sysinternals/downloads/debugview)
|
||||
and look for `[plex_patch INF]` messages:
|
||||
```
|
||||
[plex_patch INF] version guard passed (build 1.43.2.10687)
|
||||
[plex_patch INF] g_feature_bitset at 0x7FF...
|
||||
[plex_patch INF] hook installed on FeatureManager_set_features_from_uuids
|
||||
[plex_patch INF] guard thread started (interval 2000 ms)
|
||||
[plex_patch INF] feature bitset forced after set_features
|
||||
```
|
||||
|
||||
## Known values (build 1.43.2.10687)
|
||||
|
||||
| Symbol | RVA | Size | IDB name |
|
||||
|--------|-----|------|----------|
|
||||
| `g_feature_bitset` | `0x1D9E670` | 56 B (14 × DWORD) | `g_feature_bitset` |
|
||||
| `FeatureManager_set_features_from_uuids` | `0x0BC8060` | — | `FeatureManager_set_features_from_uuids` |
|
||||
| `g_feature_uuid_code_table` | `0x19C9600` | ~2.2 KB (111 × 20 B) | `g_feature_uuid_code_table` |
|
||||
| `FeatureManager_refresh_from_myplex` | `0x0BC6D10` | — | `FeatureManager_refresh_from_myplex` |
|
||||
|
||||
For a new PMS build: update `kExpectedVersion`, `kBitsetRVA`, and `kPopulatorRVA`
|
||||
in `feature_patch.h`, or add a signature-scan fallback using `sig_scan.h`.
|
||||
|
||||
---
|
||||
|
||||
## Differences from the Linux version
|
||||
|
||||
| Aspect | Linux (`src/hook.cpp`) | Windows (`windows/`) |
|
||||
|--------|----------------------|---------------------|
|
||||
| Injection | `LD_PRELOAD` | `CreateRemoteThread` + `LoadLibraryA` |
|
||||
| Module discovery | `dl_iterate_phdr` | PE header parsing (`GetModuleHandle`) |
|
||||
| Memory protection | `mmap` / `mprotect` | `VirtualAlloc` / `VirtualProtect` |
|
||||
| Cache flush | not needed (x86 coherent) | `FlushInstructionCache` (required by API) |
|
||||
| Bitset storage | 14 × uint64 (libstdc++ `std::bitset`) | 14 × uint32 (MSVC `std::bitset`) |
|
||||
| Guard thread | not needed (hook-only on Linux) | 2 s poll (belt-and-suspenders) |
|
||||
| Disassembler | Zydis (vendored, same) | Zydis (vendored, same) |
|
||||
Reference in new issue
Block a user