Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build-for-x86_64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ jobs:
curl -fLO https://dl.google.com/android/repository/android-ndk-r23b-linux.zip
unzip -qd . android-ndk-r23b-linux.zip

- name: Wrapper drift check
run: python3 scripts/check-drift.py

- name: Build
run: |
mkdir build
Expand Down
36 changes: 36 additions & 0 deletions Dockerfile.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
FROM debian:13.2

ARG TARGET_ARCH=amd64
ARG NDK_VERSION=23

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
cmake \
unzip \
git \
ca-certificates \
aria2 \
&& rm -rf /var/lib/apt/lists/*

# NOTE: No system LLVM install needed — the Android NDK bundles its own
# clang at android-ndk-r<N>b/toolchains/llvm/prebuilt/linux-x86_64/bin/

# Download Android NDK
RUN aria2c -x 16 -o ndk.zip \
https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}b-linux.zip \
&& unzip -q -d /app ndk.zip \
&& rm ndk.zip

# Copy source (no glob — explicit)
COPY cmdline.c cmdline.h wrapper.ggo ./
COPY main.c main.cpp ./
COPY wrapper.c wrapper-rootless.c ./
COPY import.h ./
COPY CMakeLists.txt ./
COPY rootfs ./rootfs

RUN mkdir -p build \
&& cmake -S /app -B /app/build -DTARGET_ARCH=${TARGET_ARCH} \
&& cmake --build /app/build -j$(nproc)
195 changes: 139 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,100 +1,172 @@
# wrapper

A tool to decrypt Apple Music songs. An active subscription is still needed.
A high-performance daemon and native library to decrypt Apple Music streams on Linux. An active subscription is required.

Supports only x86_64 and arm64 Linux.
Supports **x86_64** and **arm64** Linux.

## Installation
---

Installation methods:
## Architecture & Modes

- [Docker](#docker) (recommended)
- Prebuilt binaries (from [releases](https://github.com/WorldObservationLog/wrapper/releases) or [actions](https://github.com/WorldObservationLog/wrapper/actions))
- [Build from source](#build-from-source)
`wrapper` supports three execution modes depending on your deployment environment:

### Docker
| Mode | Binary / Target | Isolation | Description |
|---|---|---|---|
| **Host-Native (libhybris)** | `drm-native`<br>`libdrm-native.so` | None (In-Process) | **Recommended.** Loads Android Bionic `.so` libraries directly into a native glibc Linux process via libhybris. Eliminates container/proot overhead and enables in-process CGO linking. |
| **Rootless Container** | `wrapper-rootless` | User namespaces / proot | Runs unprivileged in userspace without requiring Docker or root permissions. |
| **Docker Container** | `wrapper` | Privileged container | Containerized deployment using Docker. |

Available for x86_64 and arm64. Need to download prebuilt version from releases or actions.
### Daemon Resilience & Auto-Recovery
The daemon implements a recoverable state machine (`Running`, `Scheduled`, `Refreshing`, `Failed`):
- **Non-blocking Lease Callbacks:** Lease expiry (`endLeaseCb`) and playback error (`pbErrCb`) events are queued without blocking the library thread.
- **Dedicated Recovery Worker:** Automatically coalesces lease refreshes with exponential backoff (1s → 2s → 5s → 10s → 30s).
- **Request Gating & Thread Safety:** Mutex-protected FairPlay context reads; HTTP requests gracefully gate during re-authentication rather than terminating the process with `exit(1)`.

1. Build image:
---

```
docker build --tag ghcr.io/worldobservationlog/wrapper:local .
```
## Installation & Building

### 1. Host-Native Build via Libhybris (Fastest, No NDK Required)

Compiles `drm-native` and `libdrm-native.so` directly against glibc using host `gcc`/`g++` and libhybris.

2. Login:
#### Prerequisites
- Host build tools: `gcc`, `g++`, `curl`, `patchelf`
- Built `libhybris-core.so` and linker plugin `q.so`
- libhybris headers: set `HYBRIS_INC` to the `hybris/include` directory
- Dobby (tested at commit `e9fe7fb`): `dobby.h` and a built `libdobby.a` (set `DOBBY_SRC` / `DOBBY_BUILD`; defaults `/tmp/dobby-src`, `/tmp/dobby-build`)
- On newer GCC, configure with `-DCMAKE_C_FLAGS="-include sys/time.h"`.
- Dobby's `external/logging/logging/logging.h` needs `inline` on the `Logger::Shared()` definition, or linking fails with multiple definitions.
- Optional overrides: `HYBRIS_LIB` / `LINKER_SO` (paths to `libhybris-core.so` and `q.so`), `DEPLOY_DIR_EXTRA`.

#### Build
```bash
# One-shot build (outputs to /tmp/wrapper-native)
bash build-native.sh

# Or build and deploy to a target directory:
DEPLOY_DIR=/path/to/drm bash build-and-deploy.sh
```
docker run --privileged --rm -it -v ./rootfs/data:/app/rootfs/data --entrypoint ./wrapper ghcr.io/worldobservationlog/wrapper:local -L "username:password" -H 0.0.0.0

#### Runtime Environment
When executing `drm-native` directly, configure the hybris environment paths:
```bash
export HYBRIS_LINKER_DIR=/path/to/hybris-linker
export HYBRIS_LD_LIBRARY_PATH=/path/to/rootfs/system/lib64
export HYBRIS_ANDROID_LIB64=/path/to/rootfs/system/lib64

./drm-native --base-dir /path/to/data/files
```

Quit after this (using Ctrl-C).
---

3. Run:
### 2. In-Process C / CGO Library (`drm_lib`)

```
docker run --privileged -v ./rootfs/data:/app/rootfs/data -p 10020:10020 -p 20020:20020 -p 30020:30020 -e args="-H 0.0.0.0" ghcr.io/worldobservationlog/wrapper:local
When compiled with `build-native.sh`, `libdrm-native.so` exposes a C API defined in [drm_lib.h](drm_lib.h) that can be embedded directly into Go (via CGO) or C/C++ applications without socket IPC:

```c
#include "drm_lib.h"

drm_lib_config_t cfg = {
.base_dir = "/path/to/data",
.lib64_dir = "/path/to/rootfs/system/lib64",
.auth_cb = my_auth_callback,
.state_cb = my_state_callback,
};

if (drm_lib_init(&cfg) == 0) {
/* decrypt sample in-process */
drm_lib_decrypt(kd_ctx, sample_buffer, sample_size);
drm_lib_shutdown();
}
```

---

### Build from source
### 3. Docker

1. Install dependencies:
Available for x86_64 and arm64.

- Build tools:
1. **Build image:**
```bash
docker build --tag ghcr.io/worldobservationlog/wrapper:local .
```

```
sudo apt install build-essential cmake curl unzip git
```
2. **Initial Login:**
```bash
docker run --privileged --rm -it \
-v ./rootfs/data:/app/rootfs/data \
-entrypoint ./wrapper ghcr.io/worldobservationlog/wrapper:local \
-L "username:password" -H 0.0.0.0
```
*(Exit using Ctrl-C after authentication succeeds).*

- LLVM:
3. **Run Daemon:**
```bash
docker run --privileged \
-v ./rootfs/data:/app/rootfs/data \
-p 10020:10020 -p 20020:20020 -p 30020:30020 -p 40020:40020 -p 50020:50020 -p 60020:60020 \
-e args="-H 0.0.0.0" \
ghcr.io/worldobservationlog/wrapper:local
```

```
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
```
---

- Android NDK r23b:
```
curl -fLO https://dl.google.com/android/repository/android-ndk-r23b-linux.zip
unzip -d . android-ndk-r23b-linux.zip
```
### 4. Build from Source via Android NDK (Legacy / Rootless)

2. Build:
Builds the Bionic-linked `main` executable, `wrapper`, and `wrapper-rootless`.

```
git clone https://github.com/WorldObservationLog/wrapper
cd wrapper
mkdir build
cd build
cmake ..
make -j$(nproc)
```
1. **Install dependencies:**
```bash
sudo apt install build-essential cmake curl unzip git
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
```

## Usage
2. **Download Android NDK r23b:**
```bash
curl -fLO https://dl.google.com/android/repository/android-ndk-r23b-linux.zip
unzip -d . android-ndk-r23b-linux.zip
```

```
3. **Build:**
```bash
mkdir build && cd build
cmake ..
make -j$(nproc)
```

---

## Usage & CLI Options

```text
Usage: wrapper [OPTION]...

-h, --help Print help and exit
-V, --version Print version and exit
-H, --host=STRING (default=`127.0.0.1')
-D, --decrypt-port=INT (default=`10020')
-M, --m3u8-port=INT (default=`20020')
-A, --account-port=INT (default=`30020')
-K, --key-port=INT (default=`40020')
-P, --proxy=STRING (default=`')
-L, --login=STRING (username:password)
-F, --code-from-file (default=off)
-h, --help Print help and exit
-V, --version Print version and exit
-H, --host=STRING Host to bind on (default: 127.0.0.1)
-D, --decrypt-port=INT Decryption server port (default: 10020)
-M, --m3u8-port=INT M3U8 / playlist proxy port (default: 20020)
-A, --account-port=INT Account management port (default: 30020)
-K, --key-port=INT Key service port (default: 40020)
-G, --mv-port=INT Music video port (default: 50020)
-P, --proxy=STRING HTTP proxy URL (default: none)
-L, --login=STRING Apple ID login credentials (username:password)
-F, --code-from-file Read 2FA code from file rather than stdin (default: off)
-B, --base-dir=STRING Base data directory (default: /data/data/com.apple.android.music/files)
-I, --device-info=STRING 9-field client device descriptor
```

## Services (4 TCP ports)
## Services (6 TCP ports)

| Port | Option | Protocol | Purpose |
|------|--------|----------|---------|
| 10020 | `-D` | Binary | Sample decryption: `[1B len][adamId][1B len][uri]` then loop `[4B size][ciphertext]` → plaintext |
| 20020 | `-M` | Binary | M3U8 stream URL: `[1B len][adamId digits]` → M3U8 URL |
| 30020 | `-A` | HTTP | Account info JSON |
| 40020 | `-K` | HTTP | Key service: `?adamId=&uri=` → `{contentKey, ctx, state, rcx/rax/rdx/r9/rbp}` decryption template |
| 50020 | `-G` | see source | Progressive music-video (MV) service (`new_socket_mv`) |
| 60020 | `-G` + 10000 | see source | itun FairPlay decrypt for progressive MV (`new_socket_itun`) |

### 40020 key service

Expand All @@ -108,6 +180,17 @@ curl "http://127.0.0.1:40020/?adamId=1720704575&uri=skd%3A%2F%2Fitunes.apple.com

The template is captured by a Dobby hook at the R1 entry (`libCoreLSKD+0x1d5709`) in debug builds.

---

## Development & Testing

- **Wrapper Drift Check:** Verify synchronization between privileged and rootless wrapper code:
```bash
python3 scripts/check-drift.py
```

---

## Special thanks

- Anonymous, for providing the original version of this project and the legacy Frida decryption method.
Expand Down
60 changes: 60 additions & 0 deletions build-and-deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# build-and-deploy.sh: compile drm-native and copy it, with its runtime
# dependencies, into a target directory.
#
# Usage:
# DEPLOY_DIR=/path/to/drm bash build-and-deploy.sh
#
# Optional environment variables:
# DEPLOY_DIR_EXTRA second directory to receive the same files
# NATIVE_BIN, NATIVE_SO, HYBRIS_LIB, LINKER_SO override artefact paths
#
# Layout produced in DEPLOY_DIR:
# drm-native
# libdrm-native.so
# libhybris-core.so (rpath dependency, co-located with the binary)
# hybris-linker/q.so (use as HYBRIS_LINKER_DIR)

set -euo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)"

: "${DEPLOY_DIR:?Set DEPLOY_DIR to the target directory}"
NATIVE_BIN="${NATIVE_BIN:-/tmp/wrapper-native/drm-native}"
NATIVE_SO="${NATIVE_SO:-/tmp/wrapper-native/libdrm-native.so}"
HYBRIS_LIB="${HYBRIS_LIB:-/tmp/hybris-x86_64-build/libhybris-core.so}"
LINKER_SO="${LINKER_SO:-/tmp/hybris-linker/q.so}"

echo "=== Building drm-native ==="
bash "$HERE/build-native.sh"

for f in "$NATIVE_BIN" "$NATIVE_SO" "$HYBRIS_LIB" "$LINKER_SO"; do
[[ -f "$f" ]] || { echo "ERROR: required file not found: $f"; exit 1; }
done

deploy() {
local dir="$1"
echo; echo "=== Deploying to $dir ==="
mkdir -p "$dir/hybris-linker"
cp -v "$NATIVE_BIN" "$dir/drm-native"
cp -v "$NATIVE_SO" "$dir/libdrm-native.so"
cp -v "$HYBRIS_LIB" "$dir/libhybris-core.so"
cp -v "$LINKER_SO" "$dir/hybris-linker/q.so"
chmod +x "$dir/drm-native"
# build-native.sh hard-codes an rpath under /tmp; make it relative.
if command -v patchelf &>/dev/null; then
patchelf --set-rpath '$ORIGIN' "$dir/drm-native"
echo "rpath patched to \$ORIGIN"
else
echo "WARNING: patchelf not found; binary will only run on this machine"
fi
}

deploy "$DEPLOY_DIR"
[[ -n "${DEPLOY_DIR_EXTRA:-}" ]] && deploy "$DEPLOY_DIR_EXTRA"

echo; echo "=== Done ==="
echo "Runtime env for drm-native:"
echo " HYBRIS_LINKER_DIR=$DEPLOY_DIR/hybris-linker"
echo " HYBRIS_LD_LIBRARY_PATH=<rootfs>/system/lib64"
echo " HYBRIS_ANDROID_LIB64=<rootfs>/system/lib64"
Loading