#import #import #import #import #import #include #include #include #include #include #include #include #include #include #include #include #include #import #define TARGET_MODULE "remarkable_mobile" #define IDA_BASE 0x100000000 // __ZN21QNetworkAccessManager13createRequestENS_9OperationERK15QNetworkRequestP9QIODevice // Search for strings: "local+http"; "data"; "qrc"; "unix"; "https"; // last function of the vtable of QNetworkAccessManager ("21QNetworkAccessManager") #if V3_17_0 #define QtNetworkAccessManager_createRequest 0x101673958 // sub_101673958 #elif V3_25_0 #define QtNetworkAccessManager_createRequest 0x1017FB9F4 // sub_1017FB9F4 #elif V3_26_0 #define QtNetworkAccessManager_createRequest 0x1018236C0 // sub_1018236C0 #elif V3_27_0 #define QtNetworkAccessManager_createRequest 0x101924584 // sub_101924584 #elif V3_27_1 #define QtNetworkAccessManager_createRequest 0x10192472C // sub_10192472C #endif // __ZN10QWebSocket4openERK15QNetworkRequest // Search for strings: "Invalid URL."; "wss"; "SSL Sockets are not supported on this platform."; "w"; "http"; "Unsupported WebSocket scheme: %1"; "Invalid resource name." // Then take 2nd xref // The function is called by a function referencing "openNotificationSocket" 5 times for RM logs and another one being a big WebSocket function #if V3_17_0 # define QtWebSocket_open 0x100824C5C // sub_100824C5C #elif V3_25_0 # define QtWebSocket_open 0x100526A18 // sub_100526A18 #elif V3_26_0 # define QtWebSocket_open 0x100566CF8 // sub_100566CF8 #elif V3_27_0 # define QtWebSocket_open 0x10056487C // sub_10056487C #elif V3_27_1 # define QtWebSocket_open 0x100564A24 // sub_100564A24 #endif #import "Config.h" static inline QString QStringFromNSStringSafe(NSString* str) { if (!str) return QString(); return QString::fromNSString(str); } static inline 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 *__fastcall QNetworkAccessManager::createRequest( // QtSharedPointer::ExternalRefCountData *self, // __int64 op, // const QNetworkRequest *req, // __int64 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 ) { NSLog(@"[RMHook-iOS] 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(); const QString overrideHost = QStringFromNSStringSafe(gConfiguredHost); newUrl.setHost(overrideHost); newUrl.setPort([gConfiguredPort intValue]); 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 __fastcall QWebSocket::open(QWebSocket *self, const QNetworkRequest *req) static void (*original_qWebSocket_open)( QWebSocket* self, const QNetworkRequest& req ); void hooked_qWebSocket_open( QWebSocket* self, const QNetworkRequest& req ) { NSLog(@"[RMHook-iOS] 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(); const QString overrideHost = QStringFromNSStringSafe(gConfiguredHost); newUrl.setHost(overrideHost); newUrl.setPort([gConfiguredPort intValue]); QNetworkRequest newReq(req); newReq.setUrl(newUrl); original_qWebSocket_open(self, newReq); return; } original_qWebSocket_open(self, req); } static uintptr_t findModuleBase(const char *moduleName) { uint32_t count = _dyld_image_count(); for (uint32_t i = 0; i < count; i++) { const char *name = _dyld_get_image_name(i); if (name && strstr(name, moduleName)) return (uintptr_t)_dyld_get_image_header(i); } return 0; } %ctor { @autoreleasepool { loadConfiguration(); if (gConfiguredHost.length == 0 || [gConfiguredPort intValue] == 0) { [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { showConfigAlert(); }]; } uintptr_t base = findModuleBase(TARGET_MODULE); if (!base) { NSLog(@"[RMHook-iOS] Module '%s' not found, tweak inactive.", TARGET_MODULE); return; } NSLog(@"[RMHook-iOS] '%s' base = 0x%lx", TARGET_MODULE, (unsigned long)base); uintptr_t offset = QtNetworkAccessManager_createRequest - IDA_BASE; uintptr_t addr = base + offset; MSHookFunction((void *)addr, (void *)hooked_qNetworkAccessManager_createRequest, (void **)&original_qNetworkAccessManager_createRequest); NSLog(@"[RMHook-iOS] Hooked QtNetworkAccessManager_createRequest @ 0x%lx (offset 0x%lx)", (unsigned long)addr, (unsigned long)offset); uintptr_t offset2 = QtWebSocket_open - IDA_BASE; uintptr_t addr2 = base + offset2; MSHookFunction((void *)addr2, (void *)hooked_qWebSocket_open, (void **)&original_qWebSocket_open); NSLog(@"[RMHook-iOS] Hooked QtWebSocket_open @ 0x%lx (offset 0x%lx)", (unsigned long)addr2, (unsigned long)offset2); } }