mirror of
https://github.com/NohamR/RMHook-iOS.git
synced 2026-05-24 19:59:51 +00:00
Working hook
This commit is contained in:
Binary file not shown.
Binary file not shown.
218
info.md
218
info.md
@@ -1,218 +0,0 @@
|
||||
|
||||
# Technical Notes: RMHook-iOS
|
||||
|
||||
For project overview and status, see [README.md](README.md).
|
||||
|
||||
## Network Stack Analysis
|
||||
|
||||
### URL Resolution
|
||||
The app uses a custom function to select the target hostname based on environment flags:
|
||||
|
||||
| Condition | Hostname |
|
||||
|----------------------------|--------------------------------------------|
|
||||
| Custom URL set (offset 136)| [custom].tectonic.remarkable.com |
|
||||
| QA env flag | qa.internal.cloud.remarkable.com |
|
||||
| Dev env flag | dev.internal.cloud.remarkable.com |
|
||||
| Stage env flag | stage.internal.cloud.remarkable.com |
|
||||
| Default (production) | internal.cloud.remarkable.com |
|
||||
|
||||
### network::HttpManager::setupTransaction
|
||||
Confirmed via RTTI: `network::HttpManager` (vtable entry 14). Source: `xochitl/src/xofm/libs/network/src/http-manager.cpp`.
|
||||
|
||||
#### Flow
|
||||
- Resolves hostname via custom resolver or appends `.tectonic.remarkable.com` to a base URL
|
||||
- Constructs a `network::detail::HttpTransaction` object
|
||||
- Sets up a `network::ReplyReader` for response handling
|
||||
- Dispatches via a virtual call on a queue/scheduler object
|
||||
- Logs through `rm.network.http.manager` using obfuscated string literals
|
||||
|
||||
### Networking Library Used
|
||||
This is a fully custom C++ HTTP client (`xofm/libs/network`), not NSURLSession, CFHTTPMessage, or Qt's QNetworkAccessManager.
|
||||
|
||||
| Layer | Technology |
|
||||
|-------------- |----------------------------------------------------------------------------|
|
||||
| TLS/HTTPS | Apple SecureTransport (SSLCreateContext, SSLHandshake, etc.) |
|
||||
| TCP transport | POSIX BSD sockets (_socket, _connect, _recv/_read, _write/_sendmsg) |
|
||||
| DNS | _getaddrinfo / _freeaddrinfo |
|
||||
| Event loop | CFSocket + CFRunLoop integration |
|
||||
| Async exec | GCD (dispatch_queue_create, dispatch_async) |
|
||||
| Cert pinning | SecTrustEvaluate, SecTrustSetAnchorCertificates, SecPKCS12Import |
|
||||
| TLS ALPN | SSLSetALPNProtocols / SSLCopyALPNProtocols (HTTP/2 or gRPC support) |
|
||||
|
||||
The app is Qt-based (QIOS* classes), but the networking layer bypasses Qt entirely. It talks directly to either `*.internal.cloud.remarkable.com` (REST API) or a tectonic-suffixed host with raw TLS sockets.
|
||||
|
||||
## Hook Candidate Analysis
|
||||
| Hook candidate | Pros | Cons |
|
||||
| -------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| sub_1000C81B8 | Single purpose, small, all env variants go through it | Need to handle X8 output ABI |
|
||||
| sub_1000AA31C | (setupTransaction) High level | Complex function, 0x738 bytes, hard to isolate the host field |
|
||||
| sub_10180B1BC | (string ctor) Leaf function | Called from many places, need to filter callers |
|
||||
| sub_1000AB478 | (URL builder for tectonic) | Catches tectonic-path specifically Only covers one of the two code paths (the custom-URL path) |
|
||||
|
||||
```cpp
|
||||
void __usercall sub_1000C81B8(__int64 a1@<X0>, _QWORD *a2@<X8>)
|
||||
{
|
||||
unsigned int *v4; // x19
|
||||
__int64 v5; // x21
|
||||
__int64 v6; // x22
|
||||
unsigned int v7; // w8
|
||||
unsigned int v8; // w8
|
||||
unsigned int v9; // w8
|
||||
unsigned int *v10; // x8
|
||||
unsigned int v11; // w9
|
||||
unsigned int v12; // w9
|
||||
void *v13[2]; // [xsp+8h] [xbp-58h] BYREF
|
||||
__int64 v14; // [xsp+18h] [xbp-48h]
|
||||
__int128 v15; // [xsp+20h] [xbp-40h] BYREF
|
||||
__int64 v16; // [xsp+30h] [xbp-30h]
|
||||
|
||||
if ( *(_BYTE *)(a1 + 136) )
|
||||
{
|
||||
(*(void (__fastcall **)(void **__return_ptr))(**(_QWORD **)(a1 + 128) + 112LL))(v13);
|
||||
}
|
||||
else
|
||||
{
|
||||
v13[0] = 0;
|
||||
v13[1] = 0;
|
||||
v14 = 0;
|
||||
}
|
||||
v4 = *(unsigned int **)(a1 + 240);
|
||||
v5 = *(_QWORD *)(a1 + 248);
|
||||
v6 = *(_QWORD *)(a1 + 256);
|
||||
if ( v4 )
|
||||
{
|
||||
do
|
||||
v7 = __ldaxr(v4);
|
||||
while ( __stlxr(v7 + 1, v4) );
|
||||
}
|
||||
if ( v14 )
|
||||
{
|
||||
*(_QWORD *)&v15 = v13;
|
||||
*((_QWORD *)&v15 + 1) = ".tectonic.remarkable.com";
|
||||
sub_1000AB478(a2, &v15);
|
||||
if ( !v4 )
|
||||
goto LABEL_25;
|
||||
goto LABEL_22;
|
||||
}
|
||||
if ( v6 == qword_1028F6140 && (unsigned int)sub_10180246C(v6, v5, v6, *((_QWORD *)&xmmword_1028F6130 + 1)) )
|
||||
goto LABEL_20;
|
||||
if ( v6 == qword_1028F6158 && (unsigned int)sub_10180246C(v6, v5, v6, qword_1028F6150) )
|
||||
{
|
||||
sub_10180B1BC(&v15, 35, "stage.internal.cloud.remarkable.com");
|
||||
goto LABEL_21;
|
||||
}
|
||||
if ( v6 == qword_1028F6188 && (unsigned int)sub_10180246C(v6, v5, v6, qword_1028F6180) )
|
||||
{
|
||||
sub_10180B1BC(&v15, 33, "dev.internal.cloud.remarkable.com");
|
||||
goto LABEL_21;
|
||||
}
|
||||
if ( v6 != qword_1028F6170 || !(unsigned int)sub_10180246C(v6, v5, v6, qword_1028F6168) )
|
||||
LABEL_20:
|
||||
sub_10180B1BC(&v15, 29, "internal.cloud.remarkable.com");
|
||||
else
|
||||
sub_10180B1BC(&v15, 32, "qa.internal.cloud.remarkable.com");
|
||||
LABEL_21:
|
||||
*(_OWORD *)a2 = v15;
|
||||
a2[2] = v16;
|
||||
if ( !v4 )
|
||||
goto LABEL_25;
|
||||
do
|
||||
{
|
||||
LABEL_22:
|
||||
v8 = __ldaxr(v4);
|
||||
v9 = v8 - 1;
|
||||
}
|
||||
while ( __stlxr(v9, v4) );
|
||||
if ( !v9 )
|
||||
j__free(v4);
|
||||
LABEL_25:
|
||||
v10 = (unsigned int *)v13[0];
|
||||
if ( v13[0] )
|
||||
{
|
||||
do
|
||||
{
|
||||
v11 = __ldaxr(v10);
|
||||
v12 = v11 - 1;
|
||||
}
|
||||
while ( __stlxr(v12, v10) );
|
||||
if ( !v12 )
|
||||
j__free(v13[0]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
void __usercall sub_1000C8444(_QWORD *a1@<X0>, __int64 a2@<X8>)
|
||||
{
|
||||
unsigned int *v3; // x19
|
||||
__int64 v4; // x21
|
||||
__int64 v5; // x22
|
||||
unsigned int v6; // w8
|
||||
unsigned int v7; // w8
|
||||
unsigned int v8; // w8
|
||||
__int128 v9; // [xsp+0h] [xbp-40h] BYREF
|
||||
__int64 v10; // [xsp+10h] [xbp-30h]
|
||||
|
||||
v3 = (unsigned int *)a1[30];
|
||||
v4 = a1[31];
|
||||
v5 = a1[32];
|
||||
if ( v3 )
|
||||
{
|
||||
do
|
||||
v6 = __ldaxr(v3);
|
||||
while ( __stlxr(v6 + 1, v3) );
|
||||
}
|
||||
if ( v5 == qword_1028F6140 && (unsigned int)sub_10180246C(v5, v4, v5, *((_QWORD *)&xmmword_1028F6130 + 1)) )
|
||||
goto LABEL_14;
|
||||
if ( v5 == qword_1028F6158 && (unsigned int)sub_10180246C(v5, v4, v5, qword_1028F6150) )
|
||||
{
|
||||
sub_10180B1BC(25, (__int64)"staging.my.remarkable.com", &v9);
|
||||
goto LABEL_15;
|
||||
}
|
||||
if ( v5 == qword_1028F6188 && (unsigned int)sub_10180246C(v5, v4, v5, qword_1028F6180) )
|
||||
{
|
||||
sub_10180B1BC(29, (__int64)"development.my.remarkable.com", &v9);
|
||||
goto LABEL_15;
|
||||
}
|
||||
if ( v5 != qword_1028F6170 || !(unsigned int)sub_10180246C(v5, v4, v5, qword_1028F6168) )
|
||||
LABEL_14:
|
||||
sub_10180B1BC(17, (__int64)"my.remarkable.com", &v9);
|
||||
else
|
||||
sub_10180B1BC(20, (__int64)"qa.my.remarkable.com", &v9);
|
||||
LABEL_15:
|
||||
*(_OWORD *)a2 = v9;
|
||||
*(_QWORD *)(a2 + 16) = v10;
|
||||
if ( v3 )
|
||||
{
|
||||
do
|
||||
{
|
||||
v7 = __ldaxr(v3);
|
||||
v8 = v7 - 1;
|
||||
}
|
||||
while ( __stlxr(v8, v3) );
|
||||
if ( !v8 )
|
||||
j__free(v3);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Current Status
|
||||
|
||||
```log
|
||||
2026-02-22 15:53:36.296954+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] 'remarkable_mobile' base = 0x102c28000
|
||||
2026-02-22 15:53:36.298221+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] Hooked sub_1000C81B8 @ 0x102cf01b8 (offset 0xc81b8)
|
||||
2026-02-22 15:53:36.298343+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] Hooked sub_1000C8444 @ 0x102cf0444 (offset 0xc8444)
|
||||
2026-02-22 15:53:36.416893+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] >>> sub_1000C8444 ENTER a1=0x12070d5e0 x8(out)=0x16d1d2bf0
|
||||
2026-02-22 15:53:36.416985+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] <<< sub_1000C8444 RETURN original (17 chars) = "my.remarkable.com"
|
||||
2026-02-22 15:53:36.417039+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] <<< sub_1000C8444 patched → "rm.noh.am"
|
||||
2026-02-22 15:53:36.466471+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] >>> sub_1000C8444 ENTER a1=0x12070d5e0 x8(out)=0x16d1d34b0
|
||||
2026-02-22 15:53:36.466558+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] <<< sub_1000C8444 RETURN original (17 chars) = "my.remarkable.com"
|
||||
2026-02-22 15:53:36.466604+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] <<< sub_1000C8444 patched → "rm.noh.am"
|
||||
2026-02-22 15:53:47.331051+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] >>> sub_1000C81B8 ENTER a1=0x12070d5e0 x8(out)=0x16d1497e0
|
||||
2026-02-22 15:53:47.331226+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] <<< sub_1000C81B8 RETURN original (29 chars) = "internal.cloud.remarkable.com"
|
||||
2026-02-22 15:53:47.331350+0100 0x736818 Default 0x0 46841 0 remarkable_mobile: (RMHook.dylib) [RMHook] <<< patched → "rm.noh.am"
|
||||
```
|
||||
|
||||
The above log shows that both hooks are successfully intercepting the hostname resolution and replacing it with "rm.noh.am". However, the app still fails to fetch documents..
|
||||

|
||||

|
||||
@@ -6,8 +6,13 @@ include $(THEOS)/makefiles/common.mk
|
||||
|
||||
TWEAK_NAME = RMHook
|
||||
|
||||
RMHook_FILES = Tweak.x
|
||||
RMHook_CFLAGS = -fobjc-arc
|
||||
RMHook_FILES = Tweak.xm
|
||||
RMHook_CFLAGS = -fobjc-arc -F$(HOME)/Qt/6.10.0/lib
|
||||
RMHook_CXXFLAGS = -fobjc-arc -F$(HOME)/Qt/6.10.0/lib -std=c++17
|
||||
ADDITIONAL_CFLAGS = -std=c++17 -Wno-c++17-extensions
|
||||
ADDITIONAL_CXXFLAGS = -std=c++17 -Wno-c++17-extensions -DQT_NO_VERSION_TAGGING
|
||||
ADDITIONAL_OBJCCFLAGS = -std=c++17 -Wno-c++17-extensions -DQT_NO_VERSION_TAGGING
|
||||
RMHook_LDFLAGS =
|
||||
RMHook_FRAMEWORKS = Foundation
|
||||
|
||||
include $(THEOS_MAKE_PATH)/tweak.mk
|
||||
|
||||
169
src/Tweak.x
169
src/Tweak.x
@@ -1,169 +0,0 @@
|
||||
// RMHook-iOS Tweak (POC)
|
||||
#import <substrate.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <mach-o/dyld.h>
|
||||
#import <string.h>
|
||||
#import <stdint.h>
|
||||
|
||||
// Target binary name inside the IPA
|
||||
#define TARGET_MODULE "remarkable_mobile"
|
||||
#define IDA_BASE 0x100000000ULL
|
||||
|
||||
// --- sub_1000C81B8 ---
|
||||
// void __usercall sub_1000C81B8(__int64 a1@<X0>, _QWORD *a2@<X8>)
|
||||
// X8 is ARM64's indirect-result register (not a normal parameter).
|
||||
// We use a naked trampoline to handle this.
|
||||
static void (*orig_sub_1000C81B8)(int64_t a1);
|
||||
|
||||
// Pre-call logger
|
||||
__attribute__((used))
|
||||
static void rmhook_pre(int64_t a1, uint64_t *out) {
|
||||
NSLog(@"[RMHook] >>> sub_1000C81B8 ENTER a1=0x%llx x8(out)=%p",
|
||||
(unsigned long long)a1, (void *)out);
|
||||
}
|
||||
|
||||
// Post-call logger and patcher
|
||||
__attribute__((used))
|
||||
static void rmhook_post(uint64_t *out) {
|
||||
if (!out) {
|
||||
NSLog(@"[RMHook] <<< sub_1000C81B8 RETURN (out=NULL)");
|
||||
return;
|
||||
}
|
||||
uint64_t base_ptr = out[0];
|
||||
uint64_t data_ptr = out[1];
|
||||
uint64_t char_count = out[2];
|
||||
if (data_ptr && char_count > 0 && char_count <= 4096) {
|
||||
NSString *orig = [[NSString alloc] initWithBytes:(const void *)(uintptr_t)data_ptr
|
||||
length:(NSUInteger)(char_count * 2)
|
||||
encoding:NSUTF16LittleEndianStringEncoding];
|
||||
NSLog(@"[RMHook] <<< sub_1000C81B8 RETURN original (%llu chars) = \"%@\"",
|
||||
char_count, orig ?: @"<decode error>");
|
||||
}
|
||||
// Patch: replace returned string with custom value
|
||||
NSString *replacement = @"rm.noh.am";
|
||||
NSUInteger newCount = [replacement length];
|
||||
if (data_ptr && newCount <= char_count) {
|
||||
NSData *utf16 = [replacement dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
|
||||
memcpy((void *)(uintptr_t)data_ptr, utf16.bytes, utf16.length);
|
||||
out[2] = newCount;
|
||||
*(uint64_t *)(uintptr_t)(base_ptr + 8) = newCount;
|
||||
NSLog(@"[RMHook] <<< patched → \"%@\"", replacement);
|
||||
} else {
|
||||
NSLog(@"[RMHook] <<< patch skipped (replacement too long or no buffer)");
|
||||
}
|
||||
}
|
||||
|
||||
// Naked trampoline for sub_1000C81B8
|
||||
__attribute__((naked))
|
||||
static void hook_sub_1000C81B8(void) {
|
||||
__asm__ volatile(
|
||||
"sub sp, sp, #32 \n"
|
||||
"stp x29, x30, [sp, #16] \n"
|
||||
"add x29, sp, #16 \n"
|
||||
"stp x0, x8, [sp, #0] \n"
|
||||
"mov x1, x8 \n"
|
||||
"bl _rmhook_pre \n"
|
||||
"ldp x0, x8, [sp, #0] \n"
|
||||
"adrp x9, _orig_sub_1000C81B8@PAGE \n"
|
||||
"ldr x9, [x9, _orig_sub_1000C81B8@PAGEOFF] \n"
|
||||
"blr x9 \n"
|
||||
"ldr x0, [sp, #8] \n"
|
||||
"bl _rmhook_post \n"
|
||||
"ldp x29, x30, [sp, #16] \n"
|
||||
"add sp, sp, #32 \n"
|
||||
"ret \n"
|
||||
);
|
||||
}
|
||||
|
||||
// --- sub_1000C8444 ---
|
||||
// void __usercall sub_1000C8444(_QWORD *a1@<X0>, __int64 a2@<X8>)
|
||||
static void (*orig_sub_1000C8444)(int64_t a1);
|
||||
|
||||
__attribute__((used))
|
||||
static void rmhook_pre_8444(int64_t a1, uint64_t *out) {
|
||||
NSLog(@"[RMHook] >>> sub_1000C8444 ENTER a1=0x%llx x8(out)=%p",
|
||||
(unsigned long long)a1, (void *)out);
|
||||
}
|
||||
|
||||
__attribute__((used))
|
||||
static void rmhook_post_8444(uint64_t *out) {
|
||||
if (!out) {
|
||||
NSLog(@"[RMHook] <<< sub_1000C8444 RETURN (out=NULL)");
|
||||
return;
|
||||
}
|
||||
uint64_t base_ptr = out[0];
|
||||
uint64_t data_ptr = out[1];
|
||||
uint64_t char_count = out[2];
|
||||
if (data_ptr && char_count > 0 && char_count <= 4096) {
|
||||
NSString *orig = [[NSString alloc] initWithBytes:(const void *)(uintptr_t)data_ptr
|
||||
length:(NSUInteger)(char_count * 2)
|
||||
encoding:NSUTF16LittleEndianStringEncoding];
|
||||
NSLog(@"[RMHook] <<< sub_1000C8444 RETURN original (%llu chars) = \"%@\"",
|
||||
char_count, orig ?: @"<decode error>");
|
||||
}
|
||||
NSString *replacement = @"rm.noh.am";
|
||||
NSUInteger newCount = [replacement length];
|
||||
if (data_ptr && newCount <= char_count) {
|
||||
NSData *utf16 = [replacement dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
|
||||
memcpy((void *)(uintptr_t)data_ptr, utf16.bytes, utf16.length);
|
||||
out[2] = newCount;
|
||||
*(uint64_t *)(uintptr_t)(base_ptr + 8) = newCount;
|
||||
NSLog(@"[RMHook] <<< sub_1000C8444 patched → \"%@\"", replacement);
|
||||
} else {
|
||||
NSLog(@"[RMHook] <<< sub_1000C8444 patch skipped (replacement too long or no buffer)");
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((naked))
|
||||
static void hook_sub_1000C8444(void) {
|
||||
__asm__ volatile(
|
||||
"sub sp, sp, #32 \n"
|
||||
"stp x29, x30, [sp, #16] \n"
|
||||
"add x29, sp, #16 \n"
|
||||
"stp x0, x8, [sp, #0] \n"
|
||||
"mov x1, x8 \n"
|
||||
"bl _rmhook_pre_8444 \n"
|
||||
"ldp x0, x8, [sp, #0] \n"
|
||||
"adrp x9, _orig_sub_1000C8444@PAGE \n"
|
||||
"ldr x9, [x9, _orig_sub_1000C8444@PAGEOFF] \n"
|
||||
"blr x9 \n"
|
||||
"ldr x0, [sp, #8] \n"
|
||||
"bl _rmhook_post_8444 \n"
|
||||
"ldp x29, x30, [sp, #16] \n"
|
||||
"add sp, sp, #32 \n"
|
||||
"ret \n"
|
||||
);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
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;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
%ctor {
|
||||
@autoreleasepool {
|
||||
uintptr_t base = findModuleBase(TARGET_MODULE);
|
||||
if (!base) {
|
||||
NSLog(@"[RMHook] Module '%s' not found – tweak inactive.", TARGET_MODULE);
|
||||
return;
|
||||
}
|
||||
NSLog(@"[RMHook] '%s' base = 0x%lx", TARGET_MODULE, (unsigned long)base);
|
||||
|
||||
uintptr_t offset = 0x1000C81B8ULL - IDA_BASE;
|
||||
uintptr_t addr = base + offset;
|
||||
MSHookFunction((void *)addr, (void *)hook_sub_1000C81B8, (void **)&orig_sub_1000C81B8);
|
||||
NSLog(@"[RMHook] Hooked sub_1000C81B8 @ 0x%lx (offset 0x%lx)", (unsigned long)addr, (unsigned long)offset);
|
||||
|
||||
uintptr_t offset2 = 0x1000C8444ULL - IDA_BASE;
|
||||
uintptr_t addr2 = base + offset2;
|
||||
MSHookFunction((void *)addr2, (void *)hook_sub_1000C8444, (void **)&orig_sub_1000C8444);
|
||||
NSLog(@"[RMHook] Hooked sub_1000C8444 @ 0x%lx (offset 0x%lx)", (unsigned long)addr2, (unsigned long)offset2);
|
||||
}
|
||||
}
|
||||
81
src/Tweak.xm
Normal file
81
src/Tweak.xm
Normal file
@@ -0,0 +1,81 @@
|
||||
#import <substrate.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <mach-o/dyld.h>
|
||||
#import <string.h>
|
||||
#import <stdint.h>
|
||||
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QIODevice>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/Qt>
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QAnyStringView>
|
||||
|
||||
#define TARGET_MODULE "remarkable_mobile"
|
||||
#define IDA_BASE 0x100000000
|
||||
|
||||
// __ZN21QNetworkAccessManager13createRequestENS_9OperationERK15QNetworkRequestP9QIODevice
|
||||
#define QtNetworkAccessManager_createRequest 0x1017FB9F4 // sub_1017FB9F4
|
||||
|
||||
// __ZN10QWebSocket4openERK15QNetworkRequest
|
||||
# define QtWebSocket_open 0x100526A18 // sub_100526A18
|
||||
|
||||
|
||||
// QObject *__fastcall QNetworkAccessManager::createRequest(
|
||||
// QtSharedPointer::ExternalRefCountData *a1,
|
||||
// __int64 a2,
|
||||
// const QNetworkRequest *a3,
|
||||
// __int64 a4)
|
||||
static void *(*orig_createRequest)(void *a1, int a2, const void *a3, void *a4);
|
||||
|
||||
void *hook_createRequest(void *a1, int a2, const void *a3, void *a4) {
|
||||
NSLog(@"[RMHook-iOS] createRequest called");
|
||||
return orig_createRequest(a1, a2, a3, a4);
|
||||
}
|
||||
|
||||
// void __fastcall QWebSocket::open(QWebSocket *this, const QUrl *a2, const QWebSocketHandshakeOptions *a3)
|
||||
static void (*orig_open)(void *this_ptr, const void *a2, const void *a3);
|
||||
|
||||
void hook_open(void *this_ptr, const void *a2, const void *a3) {
|
||||
NSLog(@"[RMHook-iOS] QWebSocket::open called");
|
||||
orig_open(this_ptr, a2, a3);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
%ctor {
|
||||
@autoreleasepool {
|
||||
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 *)hook_createRequest, (void **)&orig_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 *)hook_open, (void **)&orig_open);
|
||||
NSLog(@"[RMHook-iOS] Hooked QtWebSocket_open @ 0x%lx (offset 0x%lx)", (unsigned long)addr2, (unsigned long)offset2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user