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,116 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
#
|
||||
# Patch lscr.io/linuxserver/plex with the feature-unlock shared library.
|
||||
#
|
||||
# docker build -f docker/Dockerfile.linuxserver -t plex-crack:lsio .
|
||||
# docker run -d --name plex --network=host \
|
||||
# -e PUID=$(id -u) -e PGID=$(id -g) \
|
||||
# -v /srv/plex/config:/config -v /srv/plex/data:/data \
|
||||
# plex-crack:lsio
|
||||
#
|
||||
# Two stages (mirrors Dockerfile.plexinc):
|
||||
# 1. builder -- zig cross-compile plexmediaserver_crack.so (musl)
|
||||
# 2. runtime -- layer onto lscr.io/linuxserver/plex + override the s6 plex
|
||||
# service so PMS is exec'd (as user 'abc' via s6-setuidgid,
|
||||
# preserving LSIO's permission model) with LD_PRELOAD.
|
||||
#
|
||||
# Why s6-setuidgid is preserved: LSIO's run file drops to the 'abc' user
|
||||
# (uid 911) before exec'ing the PMS binary. If we don't keep that, PMS
|
||||
# would run as root, which (a) breaks LSIO's permission model, (b) prevents
|
||||
# writes to /config and /data which are chowned to abc on first start,
|
||||
# (c) makes the container harder to use (root-owned media library).
|
||||
|
||||
# ── Build args (global -- visible to every FROM) ──────────────────────────
|
||||
ARG PLEX_BASE_IMAGE=lscr.io/linuxserver/plex:latest
|
||||
|
||||
# ── Stage 1: build the musl .so (identical to plexinc) ────────────────────
|
||||
FROM debian:bookworm-slim AS builder
|
||||
|
||||
ARG ZIG_VERSION=0.13.0
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl xz-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /src
|
||||
RUN mkdir -p /src/toolchain \
|
||||
&& curl -fsSL \
|
||||
"https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" \
|
||||
-o /tmp/zig.tar.xz \
|
||||
&& tar -C /src/toolchain --strip-components=1 -xf /tmp/zig.tar.xz \
|
||||
&& rm /tmp/zig.tar.xz
|
||||
ENV PATH="/src/toolchain:${PATH}"
|
||||
|
||||
COPY build.sh ./
|
||||
COPY src ./src
|
||||
COPY third_party ./third_party
|
||||
RUN bash build.sh
|
||||
|
||||
# ── Stage 2: runtime -- patch lscr.io/linuxserver/plex ────────────────────
|
||||
FROM ${PLEX_BASE_IMAGE} AS runtime
|
||||
|
||||
ARG PLEX_BASE_IMAGE
|
||||
ARG PATCH_VERSION=dev
|
||||
LABEL org.opencontainers.image.title="plexmediaserver-crack (linuxserver)" \
|
||||
org.opencontainers.image.source="https://github.com/authrequest/Freeloader" \
|
||||
org.opencontainers.image.licenses="AGPL-3.0-or-later" \
|
||||
plex_patch.base="${PLEX_BASE_IMAGE}" \
|
||||
plex_patch.version="${PATCH_VERSION}"
|
||||
|
||||
# Sanity: refuse to build on an unfamiliar upstream layout.
|
||||
RUN set -eux; \
|
||||
PMS="/usr/lib/plexmediaserver/Plex Media Server"; \
|
||||
PMS_LIB="/usr/lib/plexmediaserver/lib"; \
|
||||
RUN_SCRIPT="/etc/s6-overlay/s6-rc.d/svc-plex/run"; \
|
||||
[ -x "${PMS}" ] || { echo "patcher: missing ${PMS} in ${PLEX_BASE_IMAGE}"; exit 1; }; \
|
||||
[ -d "${PMS_LIB}" ] || { echo "patcher: missing ${PMS_LIB}/ in ${PLEX_BASE_IMAGE}"; exit 1; }; \
|
||||
[ -f "${RUN_SCRIPT}" ] || { echo "patcher: missing ${RUN_SCRIPT} in ${PLEX_BASE_IMAGE}"; exit 1; }
|
||||
|
||||
COPY --from=builder /src/build/plexmediaserver_crack.so \
|
||||
/usr/lib/plexmediaserver/lib/plexmediaserver_crack.so
|
||||
COPY --from=builder /src/build/plexmediaserver_traffic_logger.so \
|
||||
/usr/lib/plexmediaserver/lib/plexmediaserver_traffic_logger.so
|
||||
COPY docker/wrapper.sh /usr/lib/plexmediaserver/plex-crack-wrapper.sh
|
||||
RUN chmod 0755 /usr/lib/plexmediaserver/plex-crack-wrapper.sh
|
||||
|
||||
# Override the s6 plex service run file. We preserve LSIO's s6-setuidgid
|
||||
# abc so PMS still runs as user 'abc' (uid 911) -- otherwise /config and
|
||||
# /data would be created as root and break the LSIO permission model.
|
||||
RUN set -eux; \
|
||||
RUN_SCRIPT="/etc/s6-overlay/s6-rc.d/svc-plex/run"; \
|
||||
cp "${RUN_SCRIPT}" "${RUN_SCRIPT}.orig"; \
|
||||
printf '#!/usr/bin/with-contenv bash\nexec s6-setuidgid abc /usr/lib/plexmediaserver/plex-crack-wrapper.sh\n' \
|
||||
> "${RUN_SCRIPT}"; \
|
||||
chmod 0755 "${RUN_SCRIPT}"
|
||||
|
||||
# ── Patch the PMS binary in-place to disable transcode session limits ──────
|
||||
# Patches 6 conditional-jump instructions so the limit-checking code always
|
||||
# takes the normal (no-error) path. See AGENTS.md for full RE notes.
|
||||
RUN set -eux; \
|
||||
PMS="/usr/lib/plexmediaserver/Plex Media Server"; \
|
||||
\
|
||||
# Read N bytes at a file offset as hex string (no separator). \
|
||||
rd() { dd if="${PMS}" bs=1 skip="$1" count="$2" 2>/dev/null | od -A n -t x1 | tr -d ' \n'; }; \
|
||||
\
|
||||
# sanity: verify expected bytes at each patch site (guards against version drift) \
|
||||
echo "=== verifying patch-site bytes ==="; \
|
||||
[ "$(rd 18624607 2)" = "7e0c" ] || { echo "sanity FAIL at 0x11C305F"; exit 1; }; \
|
||||
[ "$(rd 18624781 2)" = "7e08" ] || { echo "sanity FAIL at 0x11C310D"; exit 1; }; \
|
||||
[ "$(rd 18624793 2)" = "7e08" ] || { echo "sanity FAIL at 0x11C3119"; exit 1; }; \
|
||||
[ "$(rd 18624674 6)" = "0f8fc2f6ffff" ] || { echo "sanity FAIL at 0x11C30A2"; exit 1; }; \
|
||||
[ "$(rd 18624682 6)" = "0f8f1ef7ffff" ] || { echo "sanity FAIL at 0x11C30AA"; exit 1; }; \
|
||||
[ "$(rd 18624630 6)" = "0f8f9f000000" ] || { echo "sanity FAIL at 0x11C3076"; exit 1; }; \
|
||||
echo "sanity: expected bytes match, applying patches"; \
|
||||
\
|
||||
# apply patches \
|
||||
# jle -> jmp (2-byte: \353=0xEB \14=0x0C \10=0x08) \
|
||||
printf '\353\014' | dd of="${PMS}" bs=1 seek=18624607 conv=notrunc 2>/dev/null; \
|
||||
printf '\353\010' | dd of="${PMS}" bs=1 seek=18624781 conv=notrunc 2>/dev/null; \
|
||||
printf '\353\010' | dd of="${PMS}" bs=1 seek=18624793 conv=notrunc 2>/dev/null; \
|
||||
# jg -> NOP (6-byte: \220=0x90) \
|
||||
printf '\220\220\220\220\220\220' | dd of="${PMS}" bs=1 seek=18624674 conv=notrunc 2>/dev/null; \
|
||||
printf '\220\220\220\220\220\220' | dd of="${PMS}" bs=1 seek=18624682 conv=notrunc 2>/dev/null; \
|
||||
printf '\220\220\220\220\220\220' | dd of="${PMS}" bs=1 seek=18624630 conv=notrunc 2>/dev/null; \
|
||||
\
|
||||
echo "binary patch applied: transcode session limits disabled"
|
||||
Reference in new issue
Block a user