mirror of
https://github.com/NohamR/RMHook-Android.git
synced 2026-10-11 02:39:49 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbfe25eac5 | ||
|
|
c1e2e8faff | ||
|
|
309d2300de | ||
|
|
d407221fda |
37
README.md
37
README.md
@@ -24,24 +24,31 @@ RMHook-Android intercepts the reMarkable Android app's network calls at runtime
|
|||||||
**Tested and working on:**
|
**Tested and working on:**
|
||||||
- reMarkable Android app **3.27.2 (build 1461)**, split into `base.apk`, `split_config.arm64_v8a.apk`, `split_config.xxhdpi.apk` (Qt 6, arm64-v8a)
|
- reMarkable Android app **3.27.2 (build 1461)**, split into `base.apk`, `split_config.arm64_v8a.apk`, `split_config.xxhdpi.apk` (Qt 6, arm64-v8a)
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/latest.png" width="45%" />
|
||||||
|
<img src="docs/acc.png" width="45%" />
|
||||||
|
</p>
|
||||||
|
|
||||||
Only `arm64-v8a` is supported (the hook library and PLT hooks are ARM64-specific).
|
Only `arm64-v8a` is supported (the hook library and PLT hooks are ARM64-specific).
|
||||||
|
|
||||||
## Installation and usage
|
## Installation and usage
|
||||||
|
|
||||||
⚠️ **For legal reasons, this repository does not include the reMarkable app.** You must provide your own APKs (see the Building section).
|
⚠️ **For legal reasons, this repository does not include the reMarkable app.** However, the latest compiled `.so` files are available in the [Releases](https://github.com/NohamR/RMHook-Android/releases/latest) section.
|
||||||
|
|
||||||
### Prerequisites
|
### Auto installation
|
||||||
|
|
||||||
A macOS or Linux build host with:
|
Run in a terminal:
|
||||||
|
**Requirements:** `apktool`, `adb`, Android SDK build-tools (`zipalign`, `apksigner`), and `keytool` must be installed.
|
||||||
|
|
||||||
- **Android SDK Build Tools** (`zipalign`, `apksigner`) — install via Android Studio's SDK Manager or `sdkmanager "build-tools;36.0.0"`.
|
```bash
|
||||||
- **Android NDK** — e.g. `brew install --cask android-ndk` on macOS. Used to cross-compile `librmhook.so` for `aarch64-linux-android`.
|
bash <(curl -sL https://raw.githubusercontent.com/NohamR/RMHook-Android/refs/heads/main/scripts/auto-install.sh)
|
||||||
- **apktool** — `brew install apktool` on macOS. Used to inject the `System.loadLibrary` call into `MainActivity.smali`.
|
```
|
||||||
- **Python 3** — used to install Qt via `aqtinstall`.
|
|
||||||
- **Java** — for `keytool` (signing keystore generation).
|
|
||||||
- The reMarkable Android **APK splits** (base + arm64 + xxhdpi).
|
|
||||||
|
|
||||||
### Pull the APK splits from your device
|
This will download the pre-built libraries, pull the APK splits from your device, inject the hook, and install the patched app.
|
||||||
|
|
||||||
|
### Manual installation
|
||||||
|
|
||||||
|
#### Step 1: Pull the APK splits from your device
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List the paths of the installed splits
|
# List the paths of the installed splits
|
||||||
@@ -62,13 +69,13 @@ You should now have three files in `~/reMarkable/app/`:
|
|||||||
### Build
|
### Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./script/build.sh
|
./scripts/build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Inject & sign
|
### Inject & sign
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./script/inject.sh [apk_dir] # apk_dir defaults to ~/reMarkable/app
|
./scripts/inject.sh [apk_dir] # apk_dir defaults to ~/reMarkable/app
|
||||||
```
|
```
|
||||||
|
|
||||||
Patches `base.apk`'s smali to `loadLibrary("rmhook")`, bundles `librmhook.so` + `libshadowhook_nothing.so` into the arm64 split, aligns and signs everything and puts the result in `output/`.
|
Patches `base.apk`'s smali to `loadLibrary("rmhook")`, bundles `librmhook.so` + `libshadowhook_nothing.so` into the arm64 split, aligns and signs everything and puts the result in `output/`.
|
||||||
@@ -125,7 +132,7 @@ Ensure the following are set or auto-detected:
|
|||||||
### 3. Compile
|
### 3. Compile
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./script/build.sh
|
./scripts/build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
@@ -172,8 +179,8 @@ When the app attempts to connect to reMarkable's servers (e.g., `internal.cloud.
|
|||||||
|
|
||||||
- xovi-rmfakecloud: [asivery/xovi-rmfakecloud](https://github.com/asivery/xovi-rmfakecloud) - Original hooking information
|
- xovi-rmfakecloud: [asivery/xovi-rmfakecloud](https://github.com/asivery/xovi-rmfakecloud) - Original hooking information
|
||||||
- rmfakecloud: [ddvk/rmfakecloud](https://github.com/ddvk/rmfakecloud) - Self-hosted reMarkable cloud
|
- rmfakecloud: [ddvk/rmfakecloud](https://github.com/ddvk/rmfakecloud) - Self-hosted reMarkable cloud
|
||||||
- bytedance/android-inline-hook: [bytedance/android-inline-hook](https://github.com/bytedance/android-inline-hook) — ShadowHook, the inline hook library used for intercepting Qt function calls
|
- ShadowHook: [bytedance/android-inline-hook](https://github.com/bytedance/android-inline-hook) - Inline hook library for intercepting Qt function calls
|
||||||
- Dapitch666: [Dapitch666](https://github.com/dapitch666/) — initial proof-of-concept for hooking the reMarkable Android app and redirecting to rmfakecloud
|
- Dapitch666: [dapitch666](https://github.com/dapitch666/) - Initial proof-of-concept for hooking the reMarkable Android app
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
BIN
docs/acc.png
Normal file
BIN
docs/acc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
BIN
docs/latest.png
Normal file
BIN
docs/latest.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
211
scripts/auto-install.sh
Executable file
211
scripts/auto-install.sh
Executable file
@@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
REPO="NohamR/RMHook-Android"
|
||||||
|
WORKDIR="/tmp/rmhook"
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# --- Check dependencies ---
|
||||||
|
echo "[INFO] Checking dependencies..."
|
||||||
|
MISSING=()
|
||||||
|
for cmd in adb apktool keytool; do
|
||||||
|
command -v "$cmd" >/dev/null 2>&1 || MISSING+=("$cmd")
|
||||||
|
done
|
||||||
|
# Check Android SDK build-tools (zipalign + apksigner)
|
||||||
|
SDK_ROOT="${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}"
|
||||||
|
BUILD_TOOLS="$(find "$SDK_ROOT/build-tools" -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)"
|
||||||
|
if [ -z "$BUILD_TOOLS" ] || [ ! -d "$BUILD_TOOLS" ]; then
|
||||||
|
MISSING+=("Android SDK build-tools (zipalign, apksigner)")
|
||||||
|
fi
|
||||||
|
if [ ${#MISSING[@]} -gt 0 ]; then
|
||||||
|
echo "❌ Missing required tools:"
|
||||||
|
for m in "${MISSING[@]}"; do echo " - $m"; done
|
||||||
|
echo ""
|
||||||
|
echo "Install with:"
|
||||||
|
echo " brew install apktool android-platform-tools"
|
||||||
|
echo " # Android SDK build-tools via Android Studio SDK Manager"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export ANDROID_SDK_BUILD_TOOLS="$BUILD_TOOLS"
|
||||||
|
|
||||||
|
# --- Setup workspace ---
|
||||||
|
echo "[INFO] Setting up workspace..."
|
||||||
|
rm -rf "$WORKDIR"
|
||||||
|
mkdir -p "$WORKDIR/build" "$WORKDIR/libs" "$WORKDIR/config" "$WORKDIR/output"
|
||||||
|
|
||||||
|
# --- Download .so files from releases ---
|
||||||
|
echo "[INFO] Downloading librmhook.so..."
|
||||||
|
curl -sL \
|
||||||
|
-o "$WORKDIR/build/librmhook.so" \
|
||||||
|
"https://github.com/$REPO/releases/latest/download/librmhook.so"
|
||||||
|
|
||||||
|
echo "[INFO] Downloading libshadowhook_nothing.so..."
|
||||||
|
curl -sL \
|
||||||
|
-o "$WORKDIR/libs/libshadowhook_nothing.so" \
|
||||||
|
"https://github.com/$REPO/releases/latest/download/libshadowhook_nothing.so"
|
||||||
|
|
||||||
|
# --- Pull APK splits from device ---
|
||||||
|
APK_DIR="$HOME/reMarkable/app"
|
||||||
|
mkdir -p "$APK_DIR"
|
||||||
|
|
||||||
|
echo "[INFO] Detecting reMarkable app on device..."
|
||||||
|
PKG_PATHS="$(adb shell pm path com.remarkable.mobile 2>/dev/null || true)"
|
||||||
|
if [ -z "$PKG_PATHS" ]; then
|
||||||
|
echo "❌ reMarkable app not found on device. Install it from the Play Store first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Pulling APK splits..."
|
||||||
|
while IFS= read -r line; do
|
||||||
|
APK_PATH="${line#package:}"
|
||||||
|
APK_NAME="$(basename "$APK_PATH")"
|
||||||
|
echo " Pulling $APK_NAME..."
|
||||||
|
adb pull "$APK_PATH" "$APK_DIR/$APK_NAME" 2>/dev/null
|
||||||
|
done <<< "$PKG_PATHS"
|
||||||
|
|
||||||
|
if [ ! -f "$APK_DIR/base.apk" ]; then
|
||||||
|
echo "❌ Failed to pull base.apk"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Inject RMHook into APKs ---
|
||||||
|
APK_DIR_INJECT="$APK_DIR"
|
||||||
|
BUILD_DIR="$WORKDIR/build"
|
||||||
|
OUTPUT_DIR="$WORKDIR/output"
|
||||||
|
LIBS_DIR="$WORKDIR/libs"
|
||||||
|
KEYSTORE="$WORKDIR/config/rmhook.keystore"
|
||||||
|
KEYSTORE_PASS="rmhook"
|
||||||
|
KEY_ALIAS="rmhook"
|
||||||
|
ZIPALIGN="$BUILD_TOOLS/zipalign"
|
||||||
|
APKSIGNER="$BUILD_TOOLS/apksigner"
|
||||||
|
|
||||||
|
# Portable in-place sed (GNU vs BSD/macOS)
|
||||||
|
sed_inplace() {
|
||||||
|
if sed --version >/dev/null 2>&1; then
|
||||||
|
sed -i "$@"
|
||||||
|
else
|
||||||
|
sed -i '' "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[INFO] Injecting RMHook into reMarkable APKs..."
|
||||||
|
|
||||||
|
rm -rf "$OUTPUT_DIR"
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
rm -rf "$BUILD_DIR/base" "$BUILD_DIR/split_arm64" "$BUILD_DIR/split_xxhdpi"
|
||||||
|
rm -f "$BUILD_DIR"/*.apk
|
||||||
|
|
||||||
|
# --- Step 1: Decompile and patch base.apk (smali injection) ---
|
||||||
|
echo "[1/4] Patching base.apk (smali injection)..."
|
||||||
|
apktool d -r "$APK_DIR_INJECT/base.apk" -o "$BUILD_DIR/base" -f
|
||||||
|
|
||||||
|
MAIN_SMALI="$BUILD_DIR/base/smali/com/remarkable/mobile/MainActivity.smali"
|
||||||
|
if [ ! -f "$MAIN_SMALI" ]; then
|
||||||
|
echo "❌ MainActivity.smali not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed_inplace '/.method static constructor <clinit>()V/{
|
||||||
|
n
|
||||||
|
/.locals/{
|
||||||
|
a\
|
||||||
|
\
|
||||||
|
const-string v0, "rmhook"\
|
||||||
|
\
|
||||||
|
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
}' "$MAIN_SMALI"
|
||||||
|
|
||||||
|
if ! grep -q 'invoke-static {v0}, Ljava/lang/System;->loadLibrary' "$MAIN_SMALI"; then
|
||||||
|
echo "❌ Failed to inject loadLibrary(\"rmhook\") into MainActivity.smali"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Injected System.loadLibrary(\"rmhook\") into MainActivity.<clinit>"
|
||||||
|
apktool b "$BUILD_DIR/base" -o "$BUILD_DIR/base_patched.apk"
|
||||||
|
|
||||||
|
# --- Step 2: Inject libraries into arm64 split ---
|
||||||
|
echo "[2/4] Injecting ShadowHook libraries into arm64 split..."
|
||||||
|
SPLIT_ARM64="$APK_DIR_INJECT/split_config.arm64_v8a.apk"
|
||||||
|
if [ ! -f "$SPLIT_ARM64" ]; then
|
||||||
|
echo "❌ $SPLIT_ARM64 not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$BUILD_DIR/split_arm64"
|
||||||
|
unzip -q "$SPLIT_ARM64" -d "$BUILD_DIR/split_arm64"
|
||||||
|
cp "$BUILD_DIR/librmhook.so" "$BUILD_DIR/split_arm64/lib/arm64-v8a/"
|
||||||
|
cp "$LIBS_DIR/libshadowhook_nothing.so" "$BUILD_DIR/split_arm64/lib/arm64-v8a/"
|
||||||
|
rm -rf "$BUILD_DIR/split_arm64/META-INF"
|
||||||
|
(cd "$BUILD_DIR/split_arm64" && zip -q -r -0 "$BUILD_DIR/split_arm64_raw.apk" .)
|
||||||
|
|
||||||
|
# --- Step 3: Align APKs ---
|
||||||
|
echo "[3/4] Aligning APKs..."
|
||||||
|
"$ZIPALIGN" -p -f 4 "$BUILD_DIR/base_patched.apk" "$BUILD_DIR/base_aligned.apk"
|
||||||
|
"$ZIPALIGN" -p -f 4 "$BUILD_DIR/split_arm64_raw.apk" "$BUILD_DIR/split_arm64_aligned.apk"
|
||||||
|
|
||||||
|
SPLIT_XXHDPI="$APK_DIR_INJECT/split_config.xxhdpi.apk"
|
||||||
|
if [ -f "$SPLIT_XXHDPI" ]; then
|
||||||
|
mkdir -p "$BUILD_DIR/split_xxhdpi"
|
||||||
|
unzip -q "$SPLIT_XXHDPI" -d "$BUILD_DIR/split_xxhdpi"
|
||||||
|
rm -rf "$BUILD_DIR/split_xxhdpi/META-INF"
|
||||||
|
(cd "$BUILD_DIR/split_xxhdpi" && zip -q -r -0 "$BUILD_DIR/split_xxhdpi_raw.apk" .)
|
||||||
|
"$ZIPALIGN" -p -f 4 "$BUILD_DIR/split_xxhdpi_raw.apk" "$BUILD_DIR/split_xxhdpi_aligned.apk"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Step 4: Sign all APKs ---
|
||||||
|
echo "[4/4] Signing APKs..."
|
||||||
|
|
||||||
|
if [ ! -f "$KEYSTORE" ]; then
|
||||||
|
mkdir -p "$(dirname "$KEYSTORE")"
|
||||||
|
echo "🔑 Generating signing keystore..."
|
||||||
|
keytool -genkeypair -v \
|
||||||
|
-keystore "$KEYSTORE" \
|
||||||
|
-alias "$KEY_ALIAS" \
|
||||||
|
-keyalg RSA -keysize 2048 \
|
||||||
|
-validity 10000 \
|
||||||
|
-storepass "$KEYSTORE_PASS" \
|
||||||
|
-keypass "$KEYSTORE_PASS" \
|
||||||
|
-dname "CN=RMHook, OU=Dev, O=RMHook, L=Unknown, ST=Unknown, C=US" \
|
||||||
|
2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
sign_apk() {
|
||||||
|
local input="$1"
|
||||||
|
local output="$2"
|
||||||
|
"$APKSIGNER" sign \
|
||||||
|
--ks "$KEYSTORE" \
|
||||||
|
--ks-pass "pass:$KEYSTORE_PASS" \
|
||||||
|
--ks-key-alias "$KEY_ALIAS" \
|
||||||
|
--key-pass "pass:$KEYSTORE_PASS" \
|
||||||
|
--out "$output" \
|
||||||
|
"$input"
|
||||||
|
}
|
||||||
|
|
||||||
|
sign_apk "$BUILD_DIR/base_aligned.apk" "$OUTPUT_DIR/base.apk"
|
||||||
|
sign_apk "$BUILD_DIR/split_arm64_aligned.apk" "$OUTPUT_DIR/split_config.arm64_v8a.apk"
|
||||||
|
if [ -f "$BUILD_DIR/split_xxhdpi_aligned.apk" ]; then
|
||||||
|
sign_apk "$BUILD_DIR/split_xxhdpi_aligned.apk" "$OUTPUT_DIR/split_config.xxhdpi.apk"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Injection successful!"
|
||||||
|
|
||||||
|
# --- Install ---
|
||||||
|
echo "[INFO] Installing patched APKs..."
|
||||||
|
adb uninstall com.remarkable.mobile 2>/dev/null || true
|
||||||
|
adb install-multiple --no-streaming "$WORKDIR/output/base.apk" "$WORKDIR/output/split_config.arm64_v8a.apk" "$WORKDIR/output/split_config.xxhdpi.apk"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Done! RMHook is installed on your reMarkable."
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ WARNING: You MUST configure your rmfakecloud host and port!"
|
||||||
|
echo " Without this, RMHook will not work."
|
||||||
|
echo " To configure, create a file rmhook.conf with the following content (see rmhook.conf.example):"
|
||||||
|
echo " host=example.com"
|
||||||
|
echo " port=443"
|
||||||
|
echo " Then push it to your device:"
|
||||||
|
echo ""
|
||||||
|
echo " adb push rmhook.conf /sdcard/Android/data/com.remarkable.mobile/files/rmhook.conf"
|
||||||
|
echo ""
|
||||||
|
echo "🔍 To debug:"
|
||||||
|
echo " adb logcat -s 'RMHook:*'"
|
||||||
@@ -120,5 +120,5 @@ echo "✅ Compilation successful!"
|
|||||||
echo "📍 Library: $BUILD_DIR/$SO_NAME ($(ls -lh "$BUILD_DIR/$SO_NAME" | awk '{print $5}'))"
|
echo "📍 Library: $BUILD_DIR/$SO_NAME ($(ls -lh "$BUILD_DIR/$SO_NAME" | awk '{print $5}'))"
|
||||||
echo ""
|
echo ""
|
||||||
echo "🚀 To inject into the reMarkable application:"
|
echo "🚀 To inject into the reMarkable application:"
|
||||||
echo " ./script/inject.sh"
|
echo " ./scripts/inject.sh"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -60,7 +60,7 @@ echo "📦 APK source: $APK_DIR"
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [ ! -f "$BUILD_DIR/librmhook.so" ]; then
|
if [ ! -f "$BUILD_DIR/librmhook.so" ]; then
|
||||||
echo "❌ librmhook.so not found. Run ./script/build.sh first."
|
echo "❌ librmhook.so not found. Run ./scripts/build.sh first."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
Reference in New Issue
Block a user