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,88 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#include "hook.hpp"
|
||||
#include "webhook_handler.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The old constructor called hook() synchronously, but under musl the
|
||||
// LD_PRELOAD library's constructor runs *before* the main binary is loaded
|
||||
// → dl_iterate_phdr returned 0 callbacks, and /proc/self/maps doesn't yet
|
||||
// contain the PMS text segment → hook() = no-op.
|
||||
//
|
||||
// We now combine two strategies:
|
||||
//
|
||||
// 1. Try hook() immediately (best-effort – will fail if the binary
|
||||
// isn't mapped yet, which is both harmless and informative).
|
||||
//
|
||||
// 2. Override a function that PMS calls during runtime startup but well
|
||||
// after all libraries are loaded. When that override first fires, it
|
||||
// calls hook() and then passes through to the real function.
|
||||
//
|
||||
// Strategy 2 uses a trigger that's guaranteed to fire during normal PMS
|
||||
// operation but AFTER boost::uuids, epoll, and the HTTP server are all
|
||||
// initialised. We override `epoll_create1` (called by the event loop)
|
||||
// rather than `bind` (which the musl dynamic linker may reference during
|
||||
// loading, causing boost UUID crashes).
|
||||
//
|
||||
// Both strategies are idempotent: hook() is guarded by the TextSpan check
|
||||
// on the inside (returns immediately if text range not found / hooks
|
||||
// already set), and the deferred trigger is a one-shot flag.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int g_hooks_attempted = 0;
|
||||
|
||||
// ---- deferred trigger: epoll_create1 override ----
|
||||
//
|
||||
// PMS uses epoll for its event loop. The first epoll_create1 call happens
|
||||
// during server initialisation, long after the main binary is fully mapped.
|
||||
// This is a safer trigger than bind (which libc's NSS/resolver may call
|
||||
// during dlopen, triggering boost::uuids crashes).
|
||||
|
||||
#include <sys/epoll.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
typedef int (*RealEpollFn)(int);
|
||||
|
||||
static int g_epoll_triggered = 0;
|
||||
|
||||
int epoll_create1(int flags)
|
||||
{
|
||||
if(!g_epoll_triggered)
|
||||
{
|
||||
g_epoll_triggered = 1;
|
||||
hook();
|
||||
|
||||
RealEpollFn real = (RealEpollFn)dlsym(RTLD_NEXT, "epoll_create1");
|
||||
if(real)
|
||||
return real(flags);
|
||||
return syscall(SYS_epoll_create1, flags);
|
||||
}
|
||||
|
||||
RealEpollFn real = (RealEpollFn)dlsym(RTLD_NEXT, "epoll_create1");
|
||||
if(real)
|
||||
return real(flags);
|
||||
return syscall(SYS_epoll_create1, flags);
|
||||
}
|
||||
|
||||
__attribute__((constructor)) void init_so()
|
||||
{
|
||||
// This library is LD_PRELOADed into the (musl) "Plex Media Server" process.
|
||||
// Plex later spawns glibc /bin/sh helpers (Plex Tuner Service, Plex Script
|
||||
// Host, transcoders) which would inherit LD_PRELOAD and fail to load this
|
||||
// musl .so ("/bin/sh: error while loading shared libraries"). The constructor
|
||||
// runs before main() and before any child is spawned, so clearing LD_PRELOAD
|
||||
// here scopes the preload to this process only.
|
||||
unsetenv("LD_PRELOAD");
|
||||
|
||||
// Socket hooks in webhook_handler.cpp are active as soon as the library is
|
||||
// preloaded. Resolve their real libc targets before PMS startup code reads
|
||||
// from /dev/urandom; otherwise our read() interposer would fail early reads.
|
||||
webhook_handler_init();
|
||||
|
||||
// Strategy 1: skipped — hook() will be triggered by the first
|
||||
// epoll_create1 call, which happens during PMS's event loop init
|
||||
// after everything is loaded.
|
||||
}
|
||||
Reference in new issue
Block a user