mirror of
https://github.com/NohamR/RMHook-Android.git
synced 2026-10-10 18:29:42 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
13
src/Config.h
13
src/Config.h
@@ -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
|
||||
|
||||
146
src/rmhook.cpp
146
src/rmhook.cpp
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user