mirror of
https://github.com/NohamR/RMHook-Android.git
synced 2026-10-10 18:29:42 +00:00
Add RMHook core with configurable endpoint
Initialize the project with Android/Qt hook infrastructure and repository setup files
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
aqt_venv/
|
||||
rev/
|
||||
app/
|
||||
build/
|
||||
output/
|
||||
config/
|
||||
*.apk
|
||||
*.apk.idsig
|
||||
*.keystore
|
||||
libs/*.a
|
||||
libs/*.o
|
||||
libs/*.so
|
||||
.DS_Store
|
||||
rmhook.conf
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "libs/shadowhook"]
|
||||
path = libs/shadowhook
|
||||
url = https://github.com/bytedance/android-inline-hook
|
||||
320
src/Config.cpp
Normal file
320
src/Config.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
#include "Config.h"
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#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;
|
||||
|
||||
static JavaVM *gJvm = nullptr;
|
||||
static jobject gContext = nullptr;
|
||||
|
||||
extern "C" void startHooks();
|
||||
|
||||
static JNIEnv *getJNIEnv() {
|
||||
if (!gJvm) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JNIEnv *env = nullptr;
|
||||
jint result = gJvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
|
||||
if (result == JNI_EDETACHED) {
|
||||
if (gJvm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
|
||||
return nullptr;
|
||||
}
|
||||
return env;
|
||||
}
|
||||
return result == JNI_OK ? env : nullptr;
|
||||
}
|
||||
|
||||
static bool clearJavaException(JNIEnv *env, const char *operation) {
|
||||
if (!env || !env->ExceptionCheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGW("JNI exception while %s", operation);
|
||||
env->ExceptionClear();
|
||||
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')) {
|
||||
line.pop_back();
|
||||
}
|
||||
size_t first = 0;
|
||||
while (first < line.size() && (line[first] == ' ' || line[first] == '\t')) {
|
||||
++first;
|
||||
}
|
||||
return line.substr(first);
|
||||
}
|
||||
|
||||
static bool loadFromConfigFile(const char *path) {
|
||||
FILE *file = std::fopen(path, "r");
|
||||
if (!file) {
|
||||
LOGW("Could not open %s: %s", path, std::strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string host;
|
||||
int port = 0;
|
||||
char line[256];
|
||||
while (std::fgets(line, sizeof(line), file)) {
|
||||
std::string value = trimLine(line);
|
||||
if (value.compare(0, 5, "host=") == 0) {
|
||||
host = trimLine(value.c_str() + 5);
|
||||
} else if (value.compare(0, 5, "port=") == 0) {
|
||||
char *end = nullptr;
|
||||
long parsed = std::strtol(value.c_str() + 5, &end, 10);
|
||||
if (end != value.c_str() + 5 && *end == '\0') {
|
||||
port = static_cast<int>(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::fclose(file);
|
||||
|
||||
if (!setConfiguration(QString::fromUtf8(host.c_str()), port)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Loaded config from %s - Host: %s, Port: %d", path,
|
||||
gConfiguredHost.toStdString().c_str(), gConfiguredPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool loadFromSharedPrefs() {
|
||||
JNIEnv *env = getJNIEnv();
|
||||
if (!env || !gContext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jclass contextClass = env->FindClass("android/content/Context");
|
||||
jmethodID getSharedPreferences = contextClass ? env->GetMethodID(
|
||||
contextClass, "getSharedPreferences",
|
||||
"(Ljava/lang/String;I)Landroid/content/SharedPreferences;") : nullptr;
|
||||
if (clearJavaException(env, "finding SharedPreferences") || !getSharedPreferences) {
|
||||
if (contextClass) env->DeleteLocalRef(contextClass);
|
||||
return false;
|
||||
}
|
||||
|
||||
jstring prefsName = env->NewStringUTF("rmhook");
|
||||
jobject prefs = env->CallObjectMethod(gContext, getSharedPreferences, prefsName, 0);
|
||||
env->DeleteLocalRef(prefsName);
|
||||
env->DeleteLocalRef(contextClass);
|
||||
if (clearJavaException(env, "opening SharedPreferences") || !prefs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jclass prefsClass = env->FindClass("android/content/SharedPreferences");
|
||||
jmethodID getString = prefsClass ? env->GetMethodID(
|
||||
prefsClass, "getString", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") : nullptr;
|
||||
jmethodID getInt = prefsClass ? env->GetMethodID(prefsClass, "getInt", "(Ljava/lang/String;I)I") : nullptr;
|
||||
if (clearJavaException(env, "finding SharedPreferences methods") || !getString || !getInt) {
|
||||
if (prefsClass) env->DeleteLocalRef(prefsClass);
|
||||
env->DeleteLocalRef(prefs);
|
||||
return false;
|
||||
}
|
||||
|
||||
jstring hostKey = env->NewStringUTF("host");
|
||||
jstring portKey = env->NewStringUTF("port");
|
||||
jstring empty = env->NewStringUTF("");
|
||||
jstring hostValue = static_cast<jstring>(env->CallObjectMethod(prefs, getString, hostKey, empty));
|
||||
jint port = env->CallIntMethod(prefs, getInt, portKey, 0);
|
||||
bool failed = clearJavaException(env, "reading configuration values");
|
||||
env->DeleteLocalRef(hostKey);
|
||||
env->DeleteLocalRef(portKey);
|
||||
env->DeleteLocalRef(empty);
|
||||
env->DeleteLocalRef(prefsClass);
|
||||
env->DeleteLocalRef(prefs);
|
||||
if (failed || !hostValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *hostChars = env->GetStringUTFChars(hostValue, nullptr);
|
||||
QString host = hostChars ? QString::fromUtf8(hostChars) : QString();
|
||||
if (hostChars) env->ReleaseStringUTFChars(hostValue, hostChars);
|
||||
env->DeleteLocalRef(hostValue);
|
||||
|
||||
if (!setConfiguration(host, port)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Loaded config from SharedPreferences - Host: %s, Port: %d",
|
||||
gConfiguredHost.toStdString().c_str(), gConfiguredPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool getExternalConfigPath(std::string *path) {
|
||||
JNIEnv *env = getJNIEnv();
|
||||
if (!env || !gContext || !path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jclass contextClass = env->FindClass("android/content/Context");
|
||||
jmethodID getExternalFilesDir = contextClass ? env->GetMethodID(
|
||||
contextClass, "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;") : nullptr;
|
||||
if (clearJavaException(env, "finding external files directory") || !getExternalFilesDir) {
|
||||
if (contextClass) env->DeleteLocalRef(contextClass);
|
||||
return false;
|
||||
}
|
||||
|
||||
jobject directory = env->CallObjectMethod(gContext, getExternalFilesDir, nullptr);
|
||||
env->DeleteLocalRef(contextClass);
|
||||
if (clearJavaException(env, "getting external files directory") || !directory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jclass fileClass = env->FindClass("java/io/File");
|
||||
jmethodID getAbsolutePath = fileClass ? env->GetMethodID(
|
||||
fileClass, "getAbsolutePath", "()Ljava/lang/String;") : nullptr;
|
||||
jstring directoryPath = getAbsolutePath ? static_cast<jstring>(
|
||||
env->CallObjectMethod(directory, getAbsolutePath)) : nullptr;
|
||||
if (fileClass) env->DeleteLocalRef(fileClass);
|
||||
env->DeleteLocalRef(directory);
|
||||
if (clearJavaException(env, "getting external files path") || !directoryPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *pathChars = env->GetStringUTFChars(directoryPath, nullptr);
|
||||
if (pathChars) {
|
||||
*path = std::string(pathChars) + "/rmhook.conf";
|
||||
env->ReleaseStringUTFChars(directoryPath, pathChars);
|
||||
}
|
||||
env->DeleteLocalRef(directoryPath);
|
||||
return pathChars != nullptr;
|
||||
}
|
||||
|
||||
static void saveToSharedPrefs(const char *host, int port) {
|
||||
JNIEnv *env = getJNIEnv();
|
||||
if (!env || !gContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
jclass contextClass = env->FindClass("android/content/Context");
|
||||
jmethodID getSharedPreferences = contextClass ? env->GetMethodID(
|
||||
contextClass, "getSharedPreferences",
|
||||
"(Ljava/lang/String;I)Landroid/content/SharedPreferences;") : nullptr;
|
||||
jstring prefsName = env->NewStringUTF("rmhook");
|
||||
jobject prefs = getSharedPreferences ? env->CallObjectMethod(gContext, getSharedPreferences, prefsName, 0) : nullptr;
|
||||
env->DeleteLocalRef(prefsName);
|
||||
if (contextClass) env->DeleteLocalRef(contextClass);
|
||||
if (clearJavaException(env, "opening SharedPreferences") || !prefs) return;
|
||||
|
||||
jclass prefsClass = env->FindClass("android/content/SharedPreferences");
|
||||
jmethodID edit = prefsClass ? env->GetMethodID(
|
||||
prefsClass, "edit", "()Landroid/content/SharedPreferences$Editor;") : nullptr;
|
||||
jobject editor = edit ? env->CallObjectMethod(prefs, edit) : nullptr;
|
||||
if (prefsClass) env->DeleteLocalRef(prefsClass);
|
||||
env->DeleteLocalRef(prefs);
|
||||
if (clearJavaException(env, "creating SharedPreferences editor") || !editor) return;
|
||||
|
||||
jclass editorClass = env->FindClass("android/content/SharedPreferences$Editor");
|
||||
jmethodID putString = editorClass ? env->GetMethodID(editorClass, "putString",
|
||||
"(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;") : nullptr;
|
||||
jmethodID putInt = editorClass ? env->GetMethodID(editorClass, "putInt",
|
||||
"(Ljava/lang/String;I)Landroid/content/SharedPreferences$Editor;") : nullptr;
|
||||
jmethodID apply = editorClass ? env->GetMethodID(editorClass, "apply", "()V") : nullptr;
|
||||
jstring hostKey = env->NewStringUTF("host");
|
||||
jstring portKey = env->NewStringUTF("port");
|
||||
jstring hostValue = env->NewStringUTF(host);
|
||||
if (putString && putInt && apply) {
|
||||
env->CallObjectMethod(editor, putString, hostKey, hostValue);
|
||||
env->CallObjectMethod(editor, putInt, portKey, port);
|
||||
env->CallVoidMethod(editor, apply);
|
||||
clearJavaException(env, "saving configuration");
|
||||
}
|
||||
env->DeleteLocalRef(hostKey);
|
||||
env->DeleteLocalRef(portKey);
|
||||
env->DeleteLocalRef(hostValue);
|
||||
if (editorClass) env->DeleteLocalRef(editorClass);
|
||||
env->DeleteLocalRef(editor);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void loadConfiguration(void) {
|
||||
if (gConfiguredPort > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadFromSharedPrefs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string externalPath;
|
||||
if (getExternalConfigPath(&externalPath) && loadFromConfigFile(externalPath.c_str())) {
|
||||
return;
|
||||
}
|
||||
|
||||
gConfiguredHost = "example.com";
|
||||
gConfiguredPort = 443;
|
||||
LOGI("No saved config found. Using defaults - Host: %s, Port: %d",
|
||||
gConfiguredHost.toStdString().c_str(), gConfiguredPort);
|
||||
}
|
||||
|
||||
void saveConfiguration(const char *host, int port) {
|
||||
if (!host || !setConfiguration(QString::fromUtf8(host), port)) {
|
||||
LOGW("Rejected invalid configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray hostBytes = gConfiguredHost.toUtf8();
|
||||
saveToSharedPrefs(hostBytes.constData(), gConfiguredPort);
|
||||
LOGI("Saved config - Host: %s, Port: %d", gConfiguredHost.toStdString().c_str(), gConfiguredPort);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
|
||||
gJvm = vm;
|
||||
|
||||
JNIEnv *env = getJNIEnv();
|
||||
if (!env) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
jclass activityThread = env->FindClass("android/app/ActivityThread");
|
||||
jmethodID currentApplication = activityThread ? env->GetStaticMethodID(
|
||||
activityThread, "currentApplication", "()Landroid/app/Application;") : nullptr;
|
||||
jobject application = currentApplication ? env->CallStaticObjectMethod(activityThread, currentApplication) : nullptr;
|
||||
if (activityThread) env->DeleteLocalRef(activityThread);
|
||||
if (clearJavaException(env, "getting current application") || !application) {
|
||||
LOGW("JNI_OnLoad could not acquire application context");
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
gContext = env->NewGlobalRef(application);
|
||||
env->DeleteLocalRef(application);
|
||||
if (!gContext) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
loadConfiguration();
|
||||
LOGI("JNI_OnLoad: config loaded (%s:%d)", gConfiguredHost.toStdString().c_str(), gConfiguredPort);
|
||||
startHooks();
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
20
src/Config.h
Normal file
20
src/Config.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef RMHOOK_CONFIG_H
|
||||
#define RMHOOK_CONFIG_H
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
extern QString gConfiguredHost;
|
||||
extern int gConfiguredPort;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void loadConfiguration(void);
|
||||
void saveConfiguration(const char *host, int port);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // RMHOOK_CONFIG_H
|
||||
193
src/rmhook.cpp
Normal file
193
src/rmhook.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <android/log.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QIODevice>
|
||||
#include <QtCore/Qt>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QAnyStringView>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
|
||||
#include "shadowhook.h"
|
||||
#include "Config.h"
|
||||
|
||||
#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__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
|
||||
|
||||
static bool shouldPatchURL(const QString &host) {
|
||||
if (host.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// QObject *QNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData)
|
||||
static QNetworkReply* (*original_qNetworkAccessManager_createRequest)(
|
||||
QNetworkAccessManager* self,
|
||||
QNetworkAccessManager::Operation op,
|
||||
const QNetworkRequest& req,
|
||||
QIODevice* outgoingData
|
||||
);
|
||||
|
||||
QNetworkReply* hooked_qNetworkAccessManager_createRequest(
|
||||
QNetworkAccessManager* self,
|
||||
QNetworkAccessManager::Operation op,
|
||||
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);
|
||||
|
||||
if (original_qNetworkAccessManager_createRequest) {
|
||||
return original_qNetworkAccessManager_createRequest(self, op, newReq, outgoingData);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (original_qNetworkAccessManager_createRequest) {
|
||||
return original_qNetworkAccessManager_createRequest(self, op, req, outgoingData);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// void QWebSocket::open(const QNetworkRequest &req)
|
||||
static void (*original_qWebSocket_open)(
|
||||
QWebSocket* self,
|
||||
const QNetworkRequest& req
|
||||
);
|
||||
|
||||
static void *resolve_qt_symbol(const char *library, const char *symbol) {
|
||||
void *handle = dlopen(library, RTLD_NOW);
|
||||
if (!handle) {
|
||||
LOGE("Failed to load %s: %s", library, dlerror());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *address = dlsym(handle, symbol);
|
||||
if (!address) {
|
||||
LOGE("Failed to resolve %s in %s: %s", symbol, library, dlerror());
|
||||
}
|
||||
dlclose(handle);
|
||||
return address;
|
||||
}
|
||||
|
||||
void hooked_qWebSocket_open(
|
||||
QWebSocket* self,
|
||||
const QNetworkRequest& req
|
||||
) {
|
||||
LOGI("QWebSocket::open called for URL: %s", req.url().toString().toStdString().c_str());
|
||||
if (!original_qWebSocket_open) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
original_qWebSocket_open(self, req);
|
||||
}
|
||||
|
||||
static void install_hooks() {
|
||||
LOGI("Installing hooks via ShadowHook...");
|
||||
|
||||
int init_result = shadowhook_init(SHADOWHOOK_MODE_UNIQUE, false);
|
||||
if (init_result != 0) {
|
||||
LOGE("ShadowHook initialization failed: %s",
|
||||
shadowhook_to_errmsg(shadowhook_get_init_errno()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Hook QNetworkAccessManager::createRequest
|
||||
void *create_request = resolve_qt_symbol(
|
||||
"libQt6Network_arm64-v8a.so",
|
||||
"_ZN21QNetworkAccessManager13createRequestENS_9OperationERK15QNetworkRequestP9QIODevice");
|
||||
void *stub1 = shadowhook_hook_sym_addr(
|
||||
create_request,
|
||||
(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,
|
||||
(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");
|
||||
}
|
||||
|
||||
extern "C" void startHooks() {
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
if (pthread_create(&thread, &attr, (void *(*)(void *))install_hooks, nullptr) != 0) {
|
||||
LOGE("Failed to start hook thread");
|
||||
}
|
||||
pthread_attr_destroy(&attr);
|
||||
}
|
||||
|
||||
__attribute__((constructor))
|
||||
static void rmhook_init(void) {
|
||||
LOGI("RMHook loaded");
|
||||
}
|
||||
Reference in New Issue
Block a user