Refactor config access and URL hook redirects

Replace global host/port variables with a `HookConfig` struct and mutex-protected getters/setters to make configuration access thread-safe and consistently validated. In request/websocket hooks, centralize host patching with `maybeRedirect()` and a `QSet` whitelist, skipping redirects when config is invalid. Also consolidate symbol-hook setup into a shared helper with clearer error logging and library constants.
This commit is contained in:
√(noham)²
2026-08-25 23:24:26 +02:00
parent 4313f8a6eb
commit b672c6596f
3 changed files with 128 additions and 102 deletions

View File

@@ -9,18 +9,43 @@
#include <cstring>
#include <string>
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>
#define TAG "RMHook"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
QString gConfiguredHost;
int gConfiguredPort = 0;
extern "C" void startHooks();
static QMutex gConfigMutex;
static HookConfig gConfig;
HookConfig getConfiguration() {
QMutexLocker locker(&gConfigMutex);
return gConfig;
}
static bool setConfiguration(const QString &host, int port) {
HookConfig candidate{host.trimmed(), port};
if (!candidate.isValid()) {
return false;
}
QMutexLocker locker(&gConfigMutex);
gConfig = candidate;
return true;
}
static bool hasValidConfiguration() {
QMutexLocker locker(&gConfigMutex);
return gConfig.isValid();
}
static JavaVM *gJvm = nullptr;
static jobject gContext = nullptr;
extern "C" void startHooks();
static JNIEnv *getJNIEnv() {
if (!gJvm) {
return nullptr;
@@ -47,20 +72,6 @@ static bool clearJavaException(JNIEnv *env, const char *operation) {
return true;
}
static bool isValidConfig(const QString &host, int port) {
return !host.trimmed().isEmpty() && port > 0 && port <= 65535;
}
static bool setConfiguration(const QString &host, int port) {
if (!isValidConfig(host, port)) {
return false;
}
gConfiguredHost = host.trimmed();
gConfiguredPort = port;
return true;
}
static std::string trimLine(const char *value) {
std::string line(value ? value : "");
while (!line.empty() && (line.back() == '\n' || line.back() == '\r' || line.back() == ' ' || line.back() == '\t')) {
@@ -101,8 +112,9 @@ static bool loadFromConfigFile(const char *path) {
return false;
}
const HookConfig cfg = getConfiguration();
LOGI("Loaded config from %s - Host: %s, Port: %d", path,
gConfiguredHost.toStdString().c_str(), gConfiguredPort);
cfg.host.toStdString().c_str(), cfg.port);
return true;
}
@@ -163,8 +175,9 @@ static bool loadFromSharedPrefs() {
return false;
}
const HookConfig cfg = getConfiguration();
LOGI("Loaded config from SharedPreferences - Host: %s, Port: %d",
gConfiguredHost.toStdString().c_str(), gConfiguredPort);
cfg.host.toStdString().c_str(), cfg.port);
return true;
}
@@ -257,7 +270,7 @@ static void saveToSharedPrefs(const char *host, int port) {
extern "C" {
void loadConfiguration(void) {
if (gConfiguredPort > 0) {
if (hasValidConfiguration()) {
return;
}
@@ -270,10 +283,10 @@ void loadConfiguration(void) {
return;
}
gConfiguredHost = "example.com";
gConfiguredPort = 443;
setConfiguration("example.com", 443);
const HookConfig cfg = getConfiguration();
LOGI("No saved config found. Using defaults - Host: %s, Port: %d",
gConfiguredHost.toStdString().c_str(), gConfiguredPort);
cfg.host.toStdString().c_str(), cfg.port);
}
void saveConfiguration(const char *host, int port) {
@@ -282,9 +295,10 @@ void saveConfiguration(const char *host, int port) {
return;
}
QByteArray hostBytes = gConfiguredHost.toUtf8();
saveToSharedPrefs(hostBytes.constData(), gConfiguredPort);
LOGI("Saved config - Host: %s, Port: %d", gConfiguredHost.toStdString().c_str(), gConfiguredPort);
const HookConfig cfg = getConfiguration();
QByteArray hostBytes = cfg.host.toUtf8();
saveToSharedPrefs(hostBytes.constData(), cfg.port);
LOGI("Saved config - Host: %s, Port: %d", cfg.host.toStdString().c_str(), cfg.port);
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
@@ -312,7 +326,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
}
loadConfiguration();
LOGI("JNI_OnLoad: config loaded (%s:%d)", gConfiguredHost.toStdString().c_str(), gConfiguredPort);
const HookConfig cfg = getConfiguration();
LOGI("JNI_OnLoad: config loaded (%s:%d)", cfg.host.toStdString().c_str(), cfg.port);
startHooks();
return JNI_VERSION_1_6;
}

View File

@@ -3,14 +3,23 @@
#include <QtCore/QString>
extern QString gConfiguredHost;
extern int gConfiguredPort;
struct HookConfig {
QString host;
int port = 0;
bool isValid() const {
return !host.trimmed().isEmpty() && port > 0 && port <= 65535;
}
};
HookConfig getConfiguration();
#ifdef __cplusplus
extern "C" {
#endif
void loadConfiguration(void);
void saveConfiguration(const char *host, int port);
#ifdef __cplusplus

View File

@@ -8,6 +8,7 @@
#include <stdint.h>
#include <QtCore/QObject>
#include <QtCore/QSet>
#include <QtCore/QString>
#include <QtCore/QUrl>
#include <QtCore/QIODevice>
@@ -27,27 +28,54 @@
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
static const char *const kLibQtNetwork = "libQt6Network_arm64-v8a.so";
static const char *const kLibQtWebSockets = "libQt6WebSockets_arm64-v8a.so";
// https://github.com/asivery/xovi-rmfakecloud/blob/main/src/commons.cpp#L47
static const QSet<QString> &patchableHosts() {
static const QSet<QString> hosts = {
QStringLiteral("hwr-production-dot-remarkable-production.appspot.com"),
QStringLiteral("service-manager-production-dot-remarkable-production.appspot.com"),
QStringLiteral("local.appspot.com"),
QStringLiteral("my.remarkable.com"),
QStringLiteral("ping.remarkable.com"),
QStringLiteral("internal.cloud.remarkable.com"),
QStringLiteral("eu.tectonic.remarkable.com"),
QStringLiteral("backtrace-proxy.cloud.remarkable.engineering"),
QStringLiteral("dev.ping.remarkable.com"),
QStringLiteral("dev.tectonic.remarkable.com"),
QStringLiteral("dev.internal.cloud.remarkable.com"),
QStringLiteral("eu.internal.tctn.cloud.remarkable.com"),
QStringLiteral("webapp-prod.cloud.remarkable.engineering"),
};
return hosts;
}
static bool shouldPatchURL(const QString &host) {
if (host.isEmpty()) {
return false;
}
return patchableHosts().contains(host.toLower());
}
return QString(R"""(
hwr-production-dot-remarkable-production.appspot.com
service-manager-production-dot-remarkable-production.appspot.com
local.appspot.com
my.remarkable.com
ping.remarkable.com
internal.cloud.remarkable.com
eu.tectonic.remarkable.com
backtrace-proxy.cloud.remarkable.engineering
dev.ping.remarkable.com
dev.tectonic.remarkable.com
dev.internal.cloud.remarkable.com
eu.internal.tctn.cloud.remarkable.com
webapp-prod.cloud.remarkable.engineering
)""")
.contains(host, Qt::CaseInsensitive);
static QNetworkRequest maybeRedirect(const QNetworkRequest &req) {
const QString host = req.url().host();
if (!shouldPatchURL(host)) {
return req;
}
const HookConfig cfg = getConfiguration();
if (!cfg.isValid()) {
LOGW("Skipping redirect for %s: no valid configuration", host.toStdString().c_str());
return req;
}
QNetworkRequest newReq(req);
QUrl newUrl = req.url();
newUrl.setHost(cfg.host);
newUrl.setPort(cfg.port);
newReq.setUrl(newUrl);
return newReq;
}
// QObject *QNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData)
@@ -64,25 +92,12 @@ QNetworkReply* hooked_qNetworkAccessManager_createRequest(
const QNetworkRequest& req,
QIODevice* outgoingData
) {
LOGI("createRequest called for URL: %s", req.url().toString().toStdString().c_str());
const QString host = req.url().host();
if (shouldPatchURL(host)) {
QNetworkRequest newReq(req);
QUrl newUrl = req.url();
newUrl.setHost(gConfiguredHost);
newUrl.setPort(gConfiguredPort);
newReq.setUrl(newUrl);
LOGI("createRequest called for host: %s", req.url().host().toStdString().c_str());
if (original_qNetworkAccessManager_createRequest) {
return original_qNetworkAccessManager_createRequest(self, op, newReq, outgoingData);
}
if (!original_qNetworkAccessManager_createRequest) {
return nullptr;
}
if (original_qNetworkAccessManager_createRequest) {
return original_qNetworkAccessManager_createRequest(self, op, req, outgoingData);
}
return nullptr;
return original_qNetworkAccessManager_createRequest(self, op, maybeRedirect(req), outgoingData);
}
// void QWebSocket::open(const QNetworkRequest &req)
@@ -110,25 +125,30 @@ void hooked_qWebSocket_open(
QWebSocket* self,
const QNetworkRequest& req
) {
LOGI("QWebSocket::open called for URL: %s", req.url().toString().toStdString().c_str());
LOGI("QWebSocket::open called for host: %s", req.url().host().toStdString().c_str());
if (!original_qWebSocket_open) {
return;
}
original_qWebSocket_open(self, maybeRedirect(req));
}
const QString host = req.url().host();
if (shouldPatchURL(host)) {
QUrl newUrl = req.url();
newUrl.setHost(gConfiguredHost);
newUrl.setPort(gConfiguredPort);
QNetworkRequest newReq(req);
newReq.setUrl(newUrl);
original_qWebSocket_open(self, newReq);
return;
static bool hookSymbol(const char *library, const char *symbol, const char *label,
void *replacement, void **originalOut) {
void *address = resolve_qt_symbol(library, symbol);
if (!address) {
LOGE("Skipping hook for %s: symbol not found", label);
return false;
}
original_qWebSocket_open(self, req);
void *stub = shadowhook_hook_sym_addr(address, replacement, originalOut);
if (!stub) {
LOGE("Failed to hook %s: %s", label, shadowhook_to_errmsg(shadowhook_get_errno()));
return false;
}
LOGI("Hooked %s", label);
return true;
}
static void install_hooks() {
@@ -141,39 +161,21 @@ static void install_hooks() {
return;
}
// Hook QNetworkAccessManager::createRequest
void *create_request = resolve_qt_symbol(
"libQt6Network_arm64-v8a.so",
"_ZN21QNetworkAccessManager13createRequestENS_9OperationERK15QNetworkRequestP9QIODevice");
void *stub1 = shadowhook_hook_sym_addr(
create_request,
hookSymbol(
kLibQtNetwork,
"_ZN21QNetworkAccessManager13createRequestENS_9OperationERK15QNetworkRequestP9QIODevice",
"createRequest",
(void *)hooked_qNetworkAccessManager_createRequest,
(void **)&original_qNetworkAccessManager_createRequest);
if (stub1 != nullptr) {
LOGI("Hooked createRequest");
} else {
LOGE("Failed to hook createRequest: %s",
shadowhook_to_errmsg(shadowhook_get_errno()));
}
// Hook QWebSocket::open
void *websocket_open = resolve_qt_symbol(
"libQt6WebSockets_arm64-v8a.so",
"_ZN10QWebSocket4openERK15QNetworkRequest");
void *stub2 = shadowhook_hook_sym_addr(
websocket_open,
hookSymbol(
kLibQtWebSockets,
"_ZN10QWebSocket4openERK15QNetworkRequest",
"QWebSocket::open",
(void *)hooked_qWebSocket_open,
(void **)&original_qWebSocket_open);
if (stub2 != nullptr) {
LOGI("Hooked QWebSocket::open");
} else {
LOGE("Failed to hook QWebSocket::open: %s",
shadowhook_to_errmsg(shadowhook_get_errno()));
}
LOGI("All hooks active");
LOGI("Hook installation finished");
}
extern "C" void startHooks() {