Add setup docs and sample rmhook config

This commit is contained in:
√(noham)²
2026-08-26 00:10:12 +02:00
parent b7f27b1671
commit d5ef3c2fb9
2 changed files with 190 additions and 0 deletions

188
README.md Normal file
View File

@@ -0,0 +1,188 @@
# RMHook-Android
A native library injection tweak for the reMarkable Android app, enabling connection to self-hosted [rmfakecloud](https://github.com/ddvk/rmfakecloud) servers.
## Overview
RMHook-Android intercepts the reMarkable Android app's network calls at runtime and redirects them to your own rmfakecloud server. Unlike binary patching approaches, this requires **no modification of rmfakecloud** — the redirection is done transparently at the network layer, just like the iOS/macOS/Windows versions of RMHook.
## Other platforms
- **[RMHook](https://github.com/NohamR/RMHook)**: macOS Desktop
- **[RMHook-Win](https://github.com/NohamR/RMHook-Win)**: Windows Desktop
- **[RMHook-iOS](https://github.com/NohamR/RMHook-iOS)**: iOS
## Features
- Network request interception and redirection
- WebSocket connection patching
- Runtime configuration via `rmhook.conf`
- No modification of rmfakecloud required
## Compatibility
**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)
Only `arm64-v8a` is supported (the hook library and PLT hooks are ARM64-specific).
## Installation and usage
⚠️ **For legal reasons, this repository does not include the reMarkable app.** You must provide your own APKs (see the Building section).
### Prerequisites
A macOS or Linux build host with:
- **Android SDK Build Tools** (`zipalign`, `apksigner`) — install via Android Studio's SDK Manager or `sdkmanager "build-tools;36.0.0"`.
- **Android NDK** — e.g. `brew install --cask android-ndk` on macOS. Used to cross-compile `librmhook.so` for `aarch64-linux-android`.
- **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
```bash
# List the paths of the installed splits
adb shell pm path com.remarkable.mobile
# Pull each file (replace the paths with the ones returned above)
mkdir -p ~/reMarkable/app && cd ~/reMarkable/app
adb pull /data/app/~~XXXXX==/com.remarkable.mobile-YYYYY==/base.apk
adb pull /data/app/~~XXXXX==/com.remarkable.mobile-YYYYY==/split_config.arm64_v8a.apk
adb pull /data/app/~~XXXXX==/com.remarkable.mobile-YYYYY==/split_config.xxhdpi.apk
```
You should now have three files in `~/reMarkable/app/`:
- `base.apk` (Java/Kotlin bytecode and resources)
- `split_config.arm64_v8a.apk` (native libraries for ARM64 devices (Qt, reMarkable's xofm-libs))
- `split_config.xxhdpi.apk` (graphics resources)
### Build
```bash
./script/build.sh
```
### Inject & sign
```bash
./script/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/`.
### Install
```bash
adb uninstall com.remarkable.mobile
adb install-multiple --no-streaming output/base.apk output/split_config.arm64_v8a.apk output/split_config.xxhdpi.apk
```
## Configuration
Runtime configuration is loaded from `rmhook.conf` in the app's external files directory. If the file is missing or invalid, RMHook falls back to `remarkable.example.com:443`.
Push a `rmhook.conf` (see `rmhook.conf.example`) to the app's external files directory on the device:
```text
host=example.com
port=443
```
```bash
adb push rmhook.conf /sdcard/Android/data/com.remarkable.mobile/files/rmhook.conf
```
Restart the app after changing the file. The file is read once during startup. `rmhook.conf.example` is provided as a template.
## Building
### 1. Install Qt for Android
Qt headers and libraries are required at compile time (symbols are resolved at runtime via PLT hooking). Install them using [aqtinstall](https://github.com/miurahr/aqtinstall):
```bash
# Create a Python virtual environment and install aqtinstall
python3 -m venv aqt_venv
source aqt_venv/bin/activate
pip install aqtinstall
# Install Qt 6.10.2 for Android (headers + libs)
aqt install-qt all_os android 6.10.2 android_arm64_v8a -m qtwebsockets --outputdir ~/Qt
```
This installs to `~/Qt/6.10.2/android_arm64_v8a/`.
### 2. Set environment variables
Ensure the following are set or auto-detected:
- `ANDROID_NDK_HOME` — Android NDK path
- `ANDROID_SDK_BUILD_TOOLS` — Android SDK build-tools path (defaults to `~/Library/Android/sdk`)
- `QT_DIR` / `QT_VERSION` — Qt installation path and version
### 3. Compile
```bash
./script/build.sh
```
## Debugging
Stream the device logs to see output from the hooks:
```bash
adb logcat -s 'RMHook:*'
```
## How it works
RMHook-Android uses [ShadowHook](https://github.com/bytedance/android-inline-hook) to hook Qt framework functions via PLT hooking at runtime:
1. **QNetworkAccessManager::createRequest** - Intercepts HTTP/HTTPS requests
2. **QWebSocket::open** - Patches WebSocket connections
When the app attempts to connect to reMarkable's servers (e.g., `internal.cloud.remarkable.com`), the hooks redirect these requests to your configured host and port.
### Implementation notes
- `librmhook.so` compiles as C++17 and includes Qt headers (`QUrl`, `QString`, `QNetworkRequest`, `QWebSocket`) directly — no manual ABI helpers or inline assembly needed.
- ShadowHook handles memory protection, trampoline allocation, and instruction relocation automatically. Qt symbols are resolved with `dlsym()` after the Qt libraries are loaded, then passed to ShadowHook's direct-address API.
- The hook clones each `QNetworkRequest`, rewrites the host and port in the `QUrl`, and passes the modified request to the original Qt function. This avoids modifying const data directly.
- Configuration is loaded from the app-specific external files directory at startup via JNI. Host/port can be changed without rebuilding.
- The `QString` for the override host is built from the config file. Qt's copy-on-write means every `setHost()` call just bumps the refcount of the shared data, so there's no per-request allocation.
- ShadowHook is built as a static library and linked into `librmhook.so`. RMHook starts its hook thread from `JNI_OnLoad`, after shared-library constructors have completed, to avoid constructor-order races.
- ShadowHook's `libshadowhook_nothing.so` companion is packaged beside `librmhook.so`; omitting it causes `shadowhook_init()` to fail during linker initialization.
## Troubleshooting
### Build fails
- Ensure the Android NDK and SDK build-tools are installed and the environment variables are set
- Verify Qt is installed at the expected path (`~/Qt/6.10.2/android_arm64_v8a/`)
### App crashes on launch
- Check `adb logcat -s 'RMHook:*'` for error messages
- Ensure all three APK splits were installed correctly
- Verify the keystore was generated and the APKs are properly signed
### Document sync issues
- Ensure your rmfakecloud server is running and accessible
- Verify the host and port in `rmhook.conf` are correct
## Credits
- 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
- 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
- Dapitch666: [Dapitch666](https://github.com/dapitch666/) — initial proof-of-concept for hooking the reMarkable Android app and redirecting to rmfakecloud
## License
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
## Disclaimer
This project is not affiliated with, endorsed by, or sponsored by reMarkable AS. Use at your own risk. This tool modifies the reMarkable Android application and may violate the application's terms of service.
## Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.

2
rmhook.conf.example Normal file
View File

@@ -0,0 +1,2 @@
host=example.com
port=443