diff --git a/src/Config.cpp b/src/Config.cpp index 71ed35b..b3c2a20 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -9,18 +9,43 @@ #include #include +#include +#include + #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; } diff --git a/src/Config.h b/src/Config.h index 69496f9..c833c15 100644 --- a/src/Config.h +++ b/src/Config.h @@ -3,14 +3,23 @@ #include -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 diff --git a/src/rmhook.cpp b/src/rmhook.cpp index 0dbb971..ad41aa1 100644 --- a/src/rmhook.cpp +++ b/src/rmhook.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -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 &patchableHosts() { + static const QSet 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() {