mirror of
https://github.com/NohamR/RMHook-Win.git
synced 2026-05-24 19:59:43 +00:00
Add LICENSE, expand README; tidy proxy code
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Rivoirard Noham
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
119
README.md
119
README.md
@@ -1,9 +1,114 @@
|
||||
Set-ExecutionPolicy
|
||||
Run from an elevated PowerShell (script will attempt to relaunch elevated if not already) or right-click -> Run with PowerShell.
|
||||
Example: .\scripts\install-hook.ps1 -Action install
|
||||
To use a specific DLL: .\scripts\install-hook.ps1 -Action install -SourcePath "C:\path\to\your.dll"
|
||||
To restore original: .\scripts\install-hook.ps1 -Action restore
|
||||
# RMHook-Win
|
||||
|
||||
A Windows port of [RMHook](https://github.com/NohamR/RMHook) for the reMarkable Desktop application. This repo builds a proxy DLL that hooks Qt network APIs and redirects reMarkable cloud traffic to a self-hosted [rmfakecloud](https://github.com/ddvk/rmfakecloud) server.
|
||||
|
||||
install-hook.bat -Action install
|
||||
install-hook.bat -Action restore
|
||||
## Overview
|
||||
|
||||
RMHook-Win intercepts the reMarkable Desktop app's Qt networking layer and patches outgoing requests to the configured host and port. It is designed for the Windows reMarkable Desktop client and uses a DLL proxy for `paho-mqtt3as.dll`.
|
||||
|
||||
## Features
|
||||
|
||||
- Redirect reMarkable cloud HTTP(s) requests to a self-hosted rmfakecloud server
|
||||
- Patch Qt WebSocket connections used by the reMarkable app
|
||||
|
||||
## Compatibility
|
||||
|
||||
**Tested and working on:**
|
||||
<!-- - reMarkable Desktop v3.27.0 (released 2026-06-05)
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/latest.png" width="40%" />
|
||||
<img src="docs/rm.png" width="50%" />
|
||||
</p> -->
|
||||
|
||||
## Installation and usage
|
||||
|
||||
### Important legal note
|
||||
|
||||
⚠️ **For legal reasons, this repository does not include a pre-patched reMarkable app.** However, the latest compiled dylib is available in the [Releases](https://github.com/NohamR/RMHook-Win/releases/latest) section.
|
||||
|
||||
### Step 1: Build or obtain the proxy DLL
|
||||
|
||||
Build the `paho-mqtt3as-proxy` project with Visual Studio using `paho-mqtt3as-proxy.slnx`, or use an existing `paho-mqtt3as.dll` built from this repo.
|
||||
|
||||
### Step 2: Install the hook
|
||||
|
||||
Use the installer script from the `scripts` folder.
|
||||
Note: Run from an elevated PowerShell session. The installer script will request administrator privileges if needed.
|
||||
|
||||
From PowerShell:
|
||||
```powershell
|
||||
.\scripts\install-hook.ps1 -Action install
|
||||
```
|
||||
|
||||
Or with the batch wrapper:
|
||||
```cmd
|
||||
.\scripts\install-hook.bat -Action install
|
||||
```
|
||||
|
||||
If you want to install a custom DLL build:
|
||||
```powershell
|
||||
.\scripts\install-hook.ps1 -Action install -SourcePath "C:\path\to\paho-mqtt3as.dll"
|
||||
```
|
||||
|
||||
The script expects the Windows reMarkable install folder at:
|
||||
```text
|
||||
C:\Program Files\reMarkable
|
||||
```
|
||||
|
||||
### Step 3: Restore the original DLL
|
||||
|
||||
To remove the proxy and restore the original `paho-mqtt3as.dll`:
|
||||
```powershell
|
||||
.\scripts\install-hook.ps1 -Action restore
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Config path:
|
||||
```text
|
||||
%LOCALAPPDATA%\RMHook\config.json
|
||||
```
|
||||
|
||||
Example config:
|
||||
```json
|
||||
{
|
||||
"host": "your-server.example.com",
|
||||
"port": 443
|
||||
}
|
||||
```
|
||||
|
||||
If the config file does not exist, it will be created automatically with default values on first launch.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook install fails
|
||||
- Confirm the reMarkable install path is `C:\Program Files\reMarkable`
|
||||
- Run PowerShell as administrator
|
||||
- Verify the source DLL exists and is a valid proxy build
|
||||
|
||||
### App crashes or misbehaves
|
||||
- Restore the original DLL with `-Action restore`
|
||||
- Check the config file for valid JSON
|
||||
- Make sure the `host` and `port` values point to a reachable rmfakecloud server
|
||||
|
||||
## Credits
|
||||
|
||||
- MinHook: [TsudaKageyu/minhook](https://github.com/TsudaKageyu/minhook) - API hooking framework used by the project
|
||||
rmfakecloud: [ddvk/rmfakecloud](https://github.com/ddvk/rmfakecloud) - Self-hosted reMarkable cloud
|
||||
- xovi-rmfakecloud: [asivery/xovi-rmfakecloud](https://github.com/asivery/xovi-rmfakecloud) - Original hooking information
|
||||
- rm-xovi-extensions: [asivery/rm-xovi-extensions](https://github.com/asivery/rm-xovi-extensions) - Extension framework for reMarkable, used as reference for hooking Qt functions
|
||||
- [qt-resource-rebuilder](https://github.com/asivery/rm-xovi-extensions/tree/master/qt-resource-rebuilder)
|
||||
- [xovi-message-broker](https://github.com/asivery/rm-xovi-extensions/tree/master/xovi-message-broker)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file 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 Desktop application and may violate the application's terms of service.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit issues or pull requests.
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValue>
|
||||
|
||||
|
||||
static std::string GetLogPath()
|
||||
{
|
||||
char localAppData[MAX_PATH];
|
||||
@@ -36,9 +35,6 @@ static std::string GetLogPath()
|
||||
return "rmhook.log";
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Logging
|
||||
// ------------------------------------------------------------
|
||||
|
||||
static void Log(const std::string& msg)
|
||||
{
|
||||
@@ -49,11 +45,9 @@ static void Log(const std::string& msg)
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Configuration
|
||||
// ------------------------------------------------------------
|
||||
|
||||
static std::string gConfiguredHost = "example.com";
|
||||
|
||||
static int gConfiguredPort = 443;
|
||||
|
||||
static void LoadConfig()
|
||||
@@ -74,16 +68,21 @@ static void LoadConfig()
|
||||
if (!doc.isNull() && doc.isObject())
|
||||
{
|
||||
QJsonObject obj = doc.object();
|
||||
if (obj.contains("host")) gConfiguredHost = obj["host"].toString().toStdString();
|
||||
if (obj.contains("port")) gConfiguredPort = obj["port"].toInt();
|
||||
if (obj.contains("host"))
|
||||
{
|
||||
gConfiguredHost = obj["host"].toString().toStdString();
|
||||
}
|
||||
if (obj.contains("port"))
|
||||
{
|
||||
gConfiguredPort = obj["port"].toInt();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, no config exists or it failed to load.
|
||||
MessageBoxA(NULL, "First launch detected.\nUsing default config (example.com:443).\nYou can edit configuration in %LOCALAPPDATA%\\RMHook\\config.json", "RMHook Configuration", MB_OK | MB_ICONINFORMATION);
|
||||
|
||||
// Save defaults as JSON
|
||||
std::filesystem::create_directories(configPath.parent_path());
|
||||
if (file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
@@ -98,8 +97,10 @@ static void LoadConfig()
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool shouldPatchURL(const QString& host) {
|
||||
if (host.isEmpty()) {
|
||||
static inline bool shouldPatchURL(const QString& host)
|
||||
{
|
||||
if (host.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -121,9 +122,6 @@ static inline bool shouldPatchURL(const QString& host) {
|
||||
.contains(host, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Original typedefs
|
||||
// ------------------------------------------------------------
|
||||
|
||||
typedef QNetworkReply* (__fastcall* QNAM_CreateRequest_t)(
|
||||
QNetworkAccessManager* self,
|
||||
@@ -137,17 +135,9 @@ typedef void (__fastcall* QWebSocket_Open_t)(
|
||||
const QNetworkRequest& req
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Originals
|
||||
// ------------------------------------------------------------
|
||||
|
||||
static QNAM_CreateRequest_t originalCreateRequest = nullptr;
|
||||
static QWebSocket_Open_t originalWebSocketOpen = nullptr;
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Hooked createRequest
|
||||
// ------------------------------------------------------------
|
||||
|
||||
QNetworkReply* __fastcall hookedCreateRequest(
|
||||
QNetworkAccessManager* self,
|
||||
QNetworkAccessManager::Operation op,
|
||||
@@ -177,10 +167,6 @@ QNetworkReply* __fastcall hookedCreateRequest(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Hooked websocket open
|
||||
// ------------------------------------------------------------
|
||||
|
||||
void __fastcall hookedWebSocketOpen(
|
||||
QWebSocket* self,
|
||||
const QNetworkRequest& req
|
||||
@@ -208,9 +194,6 @@ void __fastcall hookedWebSocketOpen(
|
||||
originalWebSocketOpen(self, req);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------
|
||||
|
||||
void* ResolveExport(HMODULE module, const char* symbol)
|
||||
{
|
||||
@@ -225,18 +208,10 @@ void* ResolveExport(HMODULE module, const char* symbol)
|
||||
return addr;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// InstallHooks
|
||||
// ------------------------------------------------------------
|
||||
|
||||
void InstallHooks()
|
||||
{
|
||||
LoadConfig();
|
||||
|
||||
// std::string logPath = GetLogPath();
|
||||
// std::string message = "Proxy Hook Started.\nLog file: " + logPath;
|
||||
// MessageBoxA(NULL, message.c_str(), "reMarkable Proxy", MB_OK | MB_ICONINFORMATION);
|
||||
|
||||
Log("[*] Initializing MinHook");
|
||||
|
||||
if (MH_Initialize() != MH_OK)
|
||||
@@ -245,10 +220,6 @@ void InstallHooks()
|
||||
return;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Wait for Qt DLLs
|
||||
// --------------------------------------------------------
|
||||
|
||||
HMODULE qtNetwork = nullptr;
|
||||
HMODULE qtWebSockets = nullptr;
|
||||
|
||||
@@ -266,10 +237,6 @@ void InstallHooks()
|
||||
|
||||
Log("[+] Qt DLLs loaded");
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Resolve symbols
|
||||
// --------------------------------------------------------
|
||||
|
||||
void* createRequestAddr = ResolveExport(
|
||||
qtNetwork,
|
||||
"?createRequest@QNetworkAccessManager@@MEAAPEAVQNetworkReply@@W4Operation@1@AEBVQNetworkRequest@@PEAVQIODevice@@@Z"
|
||||
@@ -288,9 +255,6 @@ void InstallHooks()
|
||||
|
||||
Log("[+] Symbols resolved");
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Hook QNetworkAccessManager::createRequest
|
||||
// --------------------------------------------------------
|
||||
|
||||
if (MH_CreateHook(
|
||||
createRequestAddr,
|
||||
@@ -305,10 +269,6 @@ void InstallHooks()
|
||||
Log("[+] Hooked createRequest");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Hook QWebSocket::open
|
||||
// --------------------------------------------------------
|
||||
|
||||
if (MH_CreateHook(
|
||||
webSocketOpenAddr,
|
||||
&hookedWebSocketOpen,
|
||||
@@ -322,10 +282,6 @@ void InstallHooks()
|
||||
Log("[+] Hooked QWebSocket::open");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Enable all hooks
|
||||
// --------------------------------------------------------
|
||||
|
||||
if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK)
|
||||
{
|
||||
Log("[ERROR] Failed to enable hooks");
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
#include "common.h"
|
||||
|
||||
DWORD WINAPI DelayedHelloThread(LPVOID lpParam)
|
||||
void LoadOriginalDllFunctions()
|
||||
{
|
||||
Sleep(10000); // 10 seconds
|
||||
MessageBox(0, "Hello :)", "Proxy", MB_OK | MB_ICONINFORMATION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LoadOriginalDllFunctions() {
|
||||
paho_mqtt3as.OrignalMQTTAsync_connect = GetProcAddress(paho_mqtt3as.dll, "MQTTAsync_connect");
|
||||
paho_mqtt3as.OrignalMQTTAsync_create = GetProcAddress(paho_mqtt3as.dll, "MQTTAsync_create");
|
||||
paho_mqtt3as.OrignalMQTTAsync_createWithOptions = GetProcAddress(paho_mqtt3as.dll, "MQTTAsync_createWithOptions");
|
||||
@@ -60,7 +54,8 @@ void LoadOriginalDllFunctions() {
|
||||
paho_mqtt3as.OrignalThread_unlock_mutex = GetProcAddress(paho_mqtt3as.dll, "Thread_unlock_mutex");
|
||||
}
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
@@ -79,11 +74,13 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DLL_PROCESS_DETACH:
|
||||
{
|
||||
FreeLibrary(paho_mqtt3as.dll);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user