mirror of
https://github.com/NohamR/RMHook-Android.git
synced 2026-10-10 18:29:42 +00:00
Add setup docs and sample rmhook config
This commit is contained in:
188
README.md
Normal file
188
README.md
Normal 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
2
rmhook.conf.example
Normal file
@@ -0,0 +1,2 @@
|
||||
host=example.com
|
||||
port=443
|
||||
Reference in New Issue
Block a user